feat: more skills
This commit is contained in:
1 parent
fee46057a1
commit
348ef9f6b5
16 files changed
+143
-45
No files matched your search
+83
-1
@@ -4,7 +4,7 @@
|
|||||||
"mattpocock": {
|
"mattpocock": {
|
||||||
"url": "https://github.com/mattpocock/skills.git",
|
"url": "https://github.com/mattpocock/skills.git",
|
||||||
"ref": "main",
|
"ref": "main",
|
||||||
"rev": "d81f3a183412e71a5b1e84ca21bc1a35eea03a60",
|
"rev": "b0618bc436ad893b3c5e84e55fba86586d34a404",
|
||||||
"discovery": {
|
"discovery": {
|
||||||
"skills_dir": "skills"
|
"skills_dir": "skills"
|
||||||
},
|
},
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
"inventory": {
|
"inventory": {
|
||||||
"skills": [
|
"skills": [
|
||||||
"ask-matt",
|
"ask-matt",
|
||||||
|
"chief-of-staff",
|
||||||
"code-review",
|
"code-review",
|
||||||
"codebase-design",
|
"codebase-design",
|
||||||
"diagnosing-bugs",
|
"diagnosing-bugs",
|
||||||
@@ -55,6 +56,87 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"officecli": {
|
||||||
|
"url": "https://github.com/iOfficeAI/OfficeCLI.git",
|
||||||
|
"ref": "main",
|
||||||
|
"rev": "8d079c777ed6bea35b922c3d893758d3c2081137",
|
||||||
|
"selection": {
|
||||||
|
"mode": "all",
|
||||||
|
"exclude": [
|
||||||
|
"morph-ppt",
|
||||||
|
"morph-ppt-3d"
|
||||||
|
],
|
||||||
|
"exclude_types": [
|
||||||
|
"agents",
|
||||||
|
"commands",
|
||||||
|
"mcp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"inventory": {
|
||||||
|
"skills": [
|
||||||
|
"officecli",
|
||||||
|
"officecli-academic-paper",
|
||||||
|
"officecli-data-dashboard",
|
||||||
|
"officecli-docx",
|
||||||
|
"officecli-financial-model",
|
||||||
|
"officecli-pitch-deck",
|
||||||
|
"officecli-pptx",
|
||||||
|
"officecli-word-form",
|
||||||
|
"officecli-xlsx"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"okf": {
|
||||||
|
"url": "https://github.com/scaccogatto/okf-skills.git",
|
||||||
|
"ref": "main",
|
||||||
|
"rev": "8e3187875e66051bb52f91a5ed27342e2c3208da",
|
||||||
|
"selection": {
|
||||||
|
"mode": "all"
|
||||||
|
},
|
||||||
|
"inventory": {
|
||||||
|
"agents": [
|
||||||
|
"bundle-weaver",
|
||||||
|
"event-analyzer"
|
||||||
|
],
|
||||||
|
"skills": [
|
||||||
|
"backfill",
|
||||||
|
"okf",
|
||||||
|
"validate",
|
||||||
|
"visualize"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"warnings": [
|
||||||
|
"agents/bundle-weaver: malformed frontmatter line (missing colon): Fold event analyses into an OKF knowledge bundle in deterministic sequence. This is the",
|
||||||
|
"agents/bundle-weaver: malformed frontmatter line (missing colon): concepts over time, enforce anti-degeneration rules, and guarantee no events are lost.",
|
||||||
|
"agents/bundle-weaver: malformed frontmatter line (missing colon): only agent that writes `.okf/`, executing the reduce phase of backfill replay to evolve",
|
||||||
|
"agents/event-analyzer: malformed frontmatter line (missing colon): # Retier by forking this file; the backfill skill resolves the agent by name.",
|
||||||
|
"agents/event-analyzer: malformed frontmatter line (missing colon): # on the condition that the two explicit instructions below (flag source, evidence-only claims) stay.",
|
||||||
|
"agents/event-analyzer: malformed frontmatter line (missing colon): Analyze a single repository history event (git commit or session turn) to extract domain",
|
||||||
|
"agents/event-analyzer: malformed frontmatter line (missing colon): concepts and semantic content. Use in parallel during the map phase of OKF backfill replay",
|
||||||
|
"agents/event-analyzer: malformed frontmatter line (missing colon): to materialize decision rationale from raw commit diffs and session outcomes.",
|
||||||
|
"skills/backfill: malformed frontmatter line (missing colon): Reconstruct an OKF bundle by event-sourcing a repository's history (git log",
|
||||||
|
"skills/backfill: malformed frontmatter line (missing colon): and Claude session transcripts). Use when creating an `.okf/` bundle for an",
|
||||||
|
"skills/backfill: malformed frontmatter line (missing colon): existing repository that predates this skill, or when resuming an interrupted",
|
||||||
|
"skills/backfill: malformed frontmatter line (missing colon): knowledge bundle\", \"event-source the history\".",
|
||||||
|
"skills/okf: malformed frontmatter line (missing colon): \"capture this as a concept\", or any work in a repo that has an OKF bundle.",
|
||||||
|
"skills/okf: malformed frontmatter line (missing colon): Author, maintain, and consume Open Knowledge Format (OKF) knowledge bundles —",
|
||||||
|
"skills/okf: malformed frontmatter line (missing colon): capturing project knowledge (services, APIs, schemas, metrics, runbooks,",
|
||||||
|
"skills/okf: malformed frontmatter line (missing colon): decisions) into an OKF bundle, when updating one after code or docs change, or",
|
||||||
|
"skills/okf: malformed frontmatter line (missing colon): portable markdown + YAML frontmatter that both humans and agents read. Use when",
|
||||||
|
"skills/okf: malformed frontmatter line (missing colon): when a repository contains an `.okf/` (or other OKF) bundle that should inform",
|
||||||
|
"skills/validate: malformed frontmatter line (missing colon): Check that an Open Knowledge Format (OKF) bundle is conformant with the v0.2",
|
||||||
|
"skills/validate: malformed frontmatter line (missing colon): committing changes to one. Runs a deterministic Python checker — not an",
|
||||||
|
"skills/validate: malformed frontmatter line (missing colon): eyeball pass. Also migrates a v0.1 bundle to v0.2 in place with `--migrate`.",
|
||||||
|
"skills/validate: malformed frontmatter line (missing colon): spec (§11). Use when asked to validate, lint, or check an OKF bundle, or before",
|
||||||
|
"skills/visualize: malformed frontmatter line (missing colon): Render an Open Knowledge Format (OKF) bundle as a single self-contained,",
|
||||||
|
"skills/visualize: malformed frontmatter line (missing colon): Use when asked to visualize, graph, preview, or explore an OKF bundle.",
|
||||||
|
"skills/visualize: malformed frontmatter line (missing colon): a wiki-style detail panel with rendered markdown, v0.2 trust/lifecycle/provenance",
|
||||||
|
"skills/visualize: malformed frontmatter line (missing colon): filter and search.",
|
||||||
|
"skills/visualize: malformed frontmatter line (missing colon): interactive HTML graph (viz.html) — concepts as nodes coloured by type and",
|
||||||
|
"skills/visualize: malformed frontmatter line (missing colon): metadata, and \"Links to\" / \"Cited by\" backlinks, layout switching, per-type",
|
||||||
|
"skills/visualize: malformed frontmatter line (missing colon): sized by body length, markdown links and bundle-internal `sources` as edges,"
|
||||||
|
]
|
||||||
|
},
|
||||||
"superpowers": {
|
"superpowers": {
|
||||||
"url": "https://github.com/obra/superpowers.git",
|
"url": "https://github.com/obra/superpowers.git",
|
||||||
"ref": "main",
|
"ref": "main",
|
||||||
|
|||||||
+3
-1
@@ -8,6 +8,8 @@ disable-model-invocation: true
|
|||||||
|
|
||||||
You don't remember every skill, so ask.
|
You don't remember every skill, so ask.
|
||||||
|
|
||||||
|
Before stating what a skill does or recommending a step be skipped, read that skill's SKILL.md: the summaries here are for orientation only.
|
||||||
|
|
||||||
A **flow** is a path through the skills. Most paths run along one **main flow**, and two **on-ramps** merge onto it. Everything else is standalone, or a vocabulary layer that runs underneath.
|
A **flow** is a path through the skills. Most paths run along one **main flow**, and two **on-ramps** merge onto it. Everything else is standalone, or a vocabulary layer that runs underneath.
|
||||||
|
|
||||||
## The main flow: idea → ship
|
## The main flow: idea → ship
|
||||||
@@ -45,7 +47,7 @@ A starting situation that generates work, then merges onto the main flow.
|
|||||||
|
|
||||||
Triage is only for issues **you didn't create**: bug reports, incoming feature requests, anything that arrives raw. Tickets that `/to-tickets` produced are already agent-ready, so **don't triage them**.
|
Triage is only for issues **you didn't create**: bug reports, incoming feature requests, anything that arrives raw. Tickets that `/to-tickets` produced are already agent-ready, so **don't triage them**.
|
||||||
|
|
||||||
- **Something's broken** → **`/diagnosing-bugs`**. For the hard ones: the bug that resists a first glance, the intermittent flake, the regression that crept in between two known-good states. It refuses to theorise until it has a **tight feedback loop** (one command that already goes red on *this* bug), then fixes with a regression test. Its post-mortem hands off to **`/improve-codebase-architecture`** when the real finding is that there's no good seam to lock the bug down.
|
- **Something's broken** → **`/diagnosing-bugs`**. For the hard ones: the bug that resists a first glance, the intermittent flake, the regression that crept in between two known-good states. It refuses to theorise until it has a **tight feedback loop** (one command that already goes red on *this* bug), then fixes with a regression test. Once the fix is in, run **`/retro`** in the same session to ask what would have prevented the bug; where the real finding is that there's no good seam to lock it down, that's a job for **`/improve-codebase-architecture`**.
|
||||||
|
|
||||||
- **A huge, foggy effort: a greenfield project or a huge feature build, too big for one session** → **`/wayfinder`**, the most cognitively demanding flow here. When the way from here to the destination isn't visible yet, it charts a **shared map** of **decision tickets** on the issue tracker and resolves them one at a time, producing **decisions, not deliverables**, until the fog is pushed back and the way is clear. Where **`/grill-with-docs`** sharpens an idea you can hold in one session, wayfinder is for the idea you can't, and it's slower and denser, so save it for exactly that, never a well-scoped feature.
|
- **A huge, foggy effort: a greenfield project or a huge feature build, too big for one session** → **`/wayfinder`**, the most cognitively demanding flow here. When the way from here to the destination isn't visible yet, it charts a **shared map** of **decision tickets** on the issue tracker and resolves them one at a time, producing **decisions, not deliverables**, until the fog is pushed back and the way is clear. Where **`/grill-with-docs`** sharpens an idea you can hold in one session, wayfinder is for the idea you can't, and it's slower and denser, so save it for exactly that, never a well-scoped feature.
|
||||||
|
|
||||||
|
|||||||
+5
-3
@@ -10,7 +10,7 @@ Two-axis review of the diff between `HEAD` and a fixed point the user supplies:
|
|||||||
|
|
||||||
Both axes run as **parallel sub-agents** so they don't pollute each other's context, then this skill aggregates their findings.
|
Both axes run as **parallel sub-agents** so they don't pollute each other's context, then this skill aggregates their findings.
|
||||||
|
|
||||||
The issue tracker should have been provided to you. If `docs/agents/issue-tracker.md` is missing, tell the user to run `/setup-matt-pocock-skills`.
|
The issue tracker should have been provided to you. If not, tell the user to run `/setup-matt-pocock-skills`.
|
||||||
|
|
||||||
## Process
|
## Process
|
||||||
|
|
||||||
@@ -26,14 +26,14 @@ Before going further, confirm the fixed point resolves (`git rev-parse <fixed-po
|
|||||||
|
|
||||||
Look for the originating spec, in this order:
|
Look for the originating spec, in this order:
|
||||||
|
|
||||||
1. Issue references in the commit messages (`#123`, `Closes #45`, GitLab `!67`, etc.), fetched via the workflow in `docs/agents/issue-tracker.md`.
|
1. Issue references in the commit messages (`#123`, `Closes #45`, GitLab `!67`, etc.), fetched via the workflow in the tracker doc.
|
||||||
2. A path the user passed as an argument.
|
2. A path the user passed as an argument.
|
||||||
3. A spec file under `docs/`, `specs/`, or `.scratch/` matching the branch name or feature.
|
3. A spec file under `docs/`, `specs/`, or `.scratch/` matching the branch name or feature.
|
||||||
4. If nothing is found, ask the user where the spec is. If they say there isn't one, the **Spec** sub-agent will skip and report "no spec available".
|
4. If nothing is found, ask the user where the spec is. If they say there isn't one, the **Spec** sub-agent will skip and report "no spec available".
|
||||||
|
|
||||||
### 3. Identify the standards sources
|
### 3. Identify the standards sources
|
||||||
|
|
||||||
Anything in the repo that documents how code should be written, such as `CODING_STANDARDS.md` or `CONTRIBUTING.md`.
|
Search the repo for every file that documents how code should be written. When `CODING_STANDARDS.md` or `CONTRIBUTING.md` exists, it must be on the list.
|
||||||
|
|
||||||
On top of whatever the repo documents, the Standards axis always carries the **smell baseline** below: a fixed set of Fowler code smells (_Refactoring_, ch.3) that applies even when a repo documents nothing. Two rules bind it:
|
On top of whatever the repo documents, the Standards axis always carries the **smell baseline** below: a fixed set of Fowler code smells (_Refactoring_, ch.3) that applies even when a repo documents nothing. Two rules bind it:
|
||||||
|
|
||||||
@@ -57,6 +57,8 @@ Each smell reads *what it is* → *how to fix*; match it against the diff:
|
|||||||
|
|
||||||
### 4. Spawn both sub-agents in parallel
|
### 4. Spawn both sub-agents in parallel
|
||||||
|
|
||||||
|
Issue both sub-agent calls together, in the foreground, and aggregate the reports they return.
|
||||||
|
|
||||||
**Standards sub-agent prompt** should include:
|
**Standards sub-agent prompt** should include:
|
||||||
|
|
||||||
- The full diff command and commit list.
|
- The full diff command and commit list.
|
||||||
|
|||||||
+1
-1
@@ -122,7 +122,7 @@ A correct seam is one where the test exercises the **real bug pattern** as it oc
|
|||||||
If a correct seam exists:
|
If a correct seam exists:
|
||||||
|
|
||||||
1. Turn the minimised repro into a failing test at that seam.
|
1. Turn the minimised repro into a failing test at that seam.
|
||||||
2. Watch it fail.
|
2. Watch it fail. If you forced the red by mutating code or a fixture, `diff` against a pristine copy to prove the mutation landed before you trust it.
|
||||||
3. Apply the fix.
|
3. Apply the fix.
|
||||||
4. Watch it pass.
|
4. Watch it pass.
|
||||||
5. Re-run the Phase 1 feedback loop against the original (un-minimised) scenario.
|
5. Re-run the Phase 1 feedback loop against the original (un-minimised) scenario.
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ Format a round like so:
|
|||||||
➡️ <your recommended answer>
|
➡️ <your recommended answer>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Word each question so "yes" accepts your recommended answer.
|
||||||
|
|
||||||
Each round the user answers reshapes the tree: settled decisions push the frontier outward and unblock questions that depended on them. Recompute the frontier and ask the next round. A question whose answer depends on another question still open in this round belongs to a _later_ round, not this one.
|
Each round the user answers reshapes the tree: settled decisions push the frontier outward and unblock questions that depended on them. Recompute the frontier and ask the next round. A question whose answer depends on another question still open in this round belongs to a _later_ round, not this one.
|
||||||
|
|
||||||
Finding _facts_ is your job, never the user's. When a frontier question needs a fact from the environment (filesystem, tools, etc.), dispatch a sub-agent to find it; don't ask the user for anything you could look up yourself. Don't block on it: a running exploration is an unsettled prerequisite, so only the questions downstream of it wait for the sub-agent to report; ask the rest of the frontier now. The _decisions_ are the user's: put each to them and wait.
|
Finding _facts_ is your job, never the user's. When a frontier question needs a fact from the environment (filesystem, tools, etc.), dispatch a sub-agent to find it; don't ask the user for anything you could look up yourself. Don't block on it: a running exploration is an unsettled prerequisite, so only the questions downstream of it wait for the sub-agent to report; ask the rest of the frontier now. The _decisions_ are the user's: put each to them and wait.
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ argument-hint: "What will the next session be used for?"
|
|||||||
disable-model-invocation: true
|
disable-model-invocation: true
|
||||||
---
|
---
|
||||||
|
|
||||||
Write a handoff document summarising the current conversation so a fresh agent can continue the work. Save to the temporary directory of the user's OS - not the current workspace.
|
Write a handoff document summarising the current conversation so a fresh agent can continue the work. Save to the temporary directory of the user's OS (`$TMPDIR`, else `/tmp`; `%TEMP%` on Windows) - not the current workspace.
|
||||||
|
|
||||||
Include a "suggested skills" section in the document, naming which skills the next agent should call the Skill tool for.
|
Include a "suggested skills" section in the document, naming which skills the next agent should call the Skill tool for.
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -6,10 +6,12 @@ disable-model-invocation: true
|
|||||||
|
|
||||||
Implement the work described by the user in the spec or tickets.
|
Implement the work described by the user in the spec or tickets.
|
||||||
|
|
||||||
Use /tdd where possible, at pre-agreed seams.
|
If the user passes a ticket reference, fetch it from the issue tracker and state its title before starting. If the reference is ambiguous, ask.
|
||||||
|
|
||||||
|
Call the Skill tool with "tdd" where possible, at pre-agreed seams.
|
||||||
|
|
||||||
Run typechecking regularly, single test files regularly, and the full test suite once at the end.
|
Run typechecking regularly, single test files regularly, and the full test suite once at the end.
|
||||||
|
|
||||||
Once done, use /code-review to review the work.
|
Once done, call the Skill tool with "code-review" to review the work.
|
||||||
|
|
||||||
Commit your work to the current branch.
|
Commit your work to the current branch.
|
||||||
@@ -101,6 +101,8 @@ The block:
|
|||||||
|
|
||||||
Include the `### Triage labels` sub-block, and write `docs/agents/triage-labels.md`, only when `triage` is installed and Section B ran. When it isn't, both are omitted.
|
Include the `### Triage labels` sub-block, and write `docs/agents/triage-labels.md`, only when `triage` is installed and Section B ran. When it isn't, both are omitted.
|
||||||
|
|
||||||
|
When Section B ran on GitHub or GitLab, create each configured label the tracker lacks (`gh label create` / `glab label create`).
|
||||||
|
|
||||||
Then write the docs files using the seed templates in this skill folder as a starting point:
|
Then write the docs files using the seed templates in this skill folder as a starting point:
|
||||||
|
|
||||||
- [issue-tracker-github.md](./issue-tracker-github.md): GitHub issue tracker
|
- [issue-tracker-github.md](./issue-tracker-github.md): GitHub issue tracker
|
||||||
|
|||||||
+5
-4
@@ -5,8 +5,9 @@ Issues and specs for this repo live as GitHub issues. Use the `gh` CLI for all o
|
|||||||
## Conventions
|
## Conventions
|
||||||
|
|
||||||
- **Create an issue**: `gh issue create --title "..." --body "..."`. Use a heredoc for multi-line bodies.
|
- **Create an issue**: `gh issue create --title "..." --body "..."`. Use a heredoc for multi-line bodies.
|
||||||
- **Read an issue**: `gh issue view <number> --comments`, filtering comments by `jq` and also fetching labels.
|
- **Read an issue**: `gh issue view <number> --json number,title,body,labels,comments`.
|
||||||
- **List issues**: `gh issue list --state open --json number,title,body,labels,comments --jq '[.[] | {number, title, body, labels: [.labels[].name], comments: [.comments[].body]}]'` with appropriate `--label` and `--state` filters.
|
- **List issues**: `gh issue list --state open --json number,title,body,labels,comments --jq '[.[] | {number, title, body, labels: [.labels[].name], comments: [.comments[].body]}]'` with appropriate `--label` and `--state` filters.
|
||||||
|
- **Make an issue a sub-issue of a parent**: `gh issue create --parent <parent> ...`, or `gh issue edit <parent> --add-sub-issue <child>` afterwards (`gh` 2.94+). Older `gh`: `gh api --method POST repos/<owner>/<repo>/issues/<parent>/sub_issues -F sub_issue_id=<child-db-id>` (database id, as in **Blocking** below). Without sub-issues, put `Part of #<parent>` at the top of the child body.
|
||||||
- **Comment on an issue**: `gh issue comment <number> --body "..."`
|
- **Comment on an issue**: `gh issue comment <number> --body "..."`
|
||||||
- **Apply / remove labels**: `gh issue edit <number> --add-label "..."` / `--remove-label "..."`
|
- **Apply / remove labels**: `gh issue edit <number> --add-label "..."` / `--remove-label "..."`
|
||||||
- **Close**: `gh issue close <number> --comment "..."`
|
- **Close**: `gh issue close <number> --comment "..."`
|
||||||
@@ -20,7 +21,7 @@ Infer the repo from `git remote -v`; `gh` does this automatically when run insid
|
|||||||
When set to `yes`, PRs run through the same labels and states as issues, using the `gh pr` equivalents:
|
When set to `yes`, PRs run through the same labels and states as issues, using the `gh pr` equivalents:
|
||||||
|
|
||||||
- **Read a PR**: `gh pr view <number> --comments` and `gh pr diff <number>` for the diff.
|
- **Read a PR**: `gh pr view <number> --comments` and `gh pr diff <number>` for the diff.
|
||||||
- **List external PRs for triage**: `gh pr list --state open --json number,title,body,labels,author,authorAssociation,comments` then keep only `authorAssociation` of `CONTRIBUTOR`, `FIRST_TIME_CONTRIBUTOR`, or `NONE` (drop `OWNER`/`MEMBER`/`COLLABORATOR`).
|
- **List external PRs for triage**: `gh api --paginate 'repos/{owner}/{repo}/pulls?state=open' --jq '.[] | select(.author_association | IN("OWNER","MEMBER","COLLABORATOR") | not) | {number, title, author: .user.login, author_association, labels: [.labels[].name]}'`.
|
||||||
- **Comment / label / close**: `gh pr comment`, `gh pr edit --add-label`/`--remove-label`, `gh pr close`.
|
- **Comment / label / close**: `gh pr comment`, `gh pr edit --add-label`/`--remove-label`, `gh pr close`.
|
||||||
|
|
||||||
GitHub shares one number space across issues and PRs, so a bare `#42` may be either: resolve with `gh pr view 42` and fall back to `gh issue view 42`.
|
GitHub shares one number space across issues and PRs, so a bare `#42` may be either: resolve with `gh pr view 42` and fall back to `gh issue view 42`.
|
||||||
@@ -31,14 +32,14 @@ Create a GitHub issue.
|
|||||||
|
|
||||||
## When a skill says "fetch the relevant ticket"
|
## When a skill says "fetch the relevant ticket"
|
||||||
|
|
||||||
Run `gh issue view <number> --comments`.
|
Read it as in **Read an issue** above.
|
||||||
|
|
||||||
## Wayfinding operations
|
## Wayfinding operations
|
||||||
|
|
||||||
Used by `/wayfinder`. The **map** is a single issue with **child** issues as tickets.
|
Used by `/wayfinder`. The **map** is a single issue with **child** issues as tickets.
|
||||||
|
|
||||||
- **Map**: a single issue labelled `wayfinder:map`, holding the Notes / Decisions-so-far / Fog body. `gh issue create --label wayfinder:map`.
|
- **Map**: a single issue labelled `wayfinder:map`, holding the Notes / Decisions-so-far / Fog body. `gh issue create --label wayfinder:map`.
|
||||||
- **Child ticket**: an issue linked to the map as a GitHub sub-issue (`gh api` on the sub-issues endpoint). Where sub-issues aren't enabled, add the child to a task list in the map body and put `Part of #<map>` at the top of the child body. Labels: `wayfinder:<type>` (`research`/`prototype`/`grilling`/`task`). Once claimed, the ticket is assigned to the driving dev.
|
- **Child ticket**: an issue linked to the map as a GitHub sub-issue (see **Make an issue a sub-issue of a parent**). Where sub-issues aren't enabled, add the child to a task list in the map body and put `Part of #<map>` at the top of the child body. Labels: `wayfinder:<type>` (`research`/`prototype`/`grilling`/`task`). Once claimed, the ticket is assigned to the driving dev.
|
||||||
- **Blocking**: GitHub's **native issue dependencies**, the canonical, UI-visible representation. Add an edge with `gh api --method POST repos/<owner>/<repo>/issues/<child>/dependencies/blocked_by -F issue_id=<blocker-db-id>`, where `<blocker-db-id>` is the blocker's numeric **database id** (`gh api repos/<owner>/<repo>/issues/<n> --jq .id`, _not_ the `#number` or `node_id`). GitHub reports `issue_dependencies_summary.blocked_by` (open blockers only, the live gate). Where dependencies aren't available, fall back to a `Blocked by: #<n>, #<n>` line at the top of the child body. A ticket is unblocked when every blocker is closed.
|
- **Blocking**: GitHub's **native issue dependencies**, the canonical, UI-visible representation. Add an edge with `gh api --method POST repos/<owner>/<repo>/issues/<child>/dependencies/blocked_by -F issue_id=<blocker-db-id>`, where `<blocker-db-id>` is the blocker's numeric **database id** (`gh api repos/<owner>/<repo>/issues/<n> --jq .id`, _not_ the `#number` or `node_id`). GitHub reports `issue_dependencies_summary.blocked_by` (open blockers only, the live gate). Where dependencies aren't available, fall back to a `Blocked by: #<n>, #<n>` line at the top of the child body. A ticket is unblocked when every blocker is closed.
|
||||||
- **Frontier query**: list the map's open children (`gh issue list --state open`, scoped to the map's sub-issues / task list), drop any with an open blocker (`issue_dependencies_summary.blocked_by > 0`, or an open issue in the `Blocked by` line) or an assignee; first in map order wins.
|
- **Frontier query**: list the map's open children (`gh issue list --state open`, scoped to the map's sub-issues / task list), drop any with an open blocker (`issue_dependencies_summary.blocked_by > 0`, or an open issue in the `Blocked by` line) or an assignee; first in map order wins.
|
||||||
- **Claim**: `gh issue edit <n> --add-assignee @me`, the session's first write.
|
- **Claim**: `gh issue edit <n> --add-assignee @me`, the session's first write.
|
||||||
|
|||||||
+2
-2
@@ -6,7 +6,7 @@ Issues and specs for this repo live as GitLab issues. Use the [`glab`](https://g
|
|||||||
|
|
||||||
- **Create an issue**: `glab issue create --title "..." --description "..."`. Use a heredoc for multi-line descriptions. Pass `--description -` to open an editor.
|
- **Create an issue**: `glab issue create --title "..." --description "..."`. Use a heredoc for multi-line descriptions. Pass `--description -` to open an editor.
|
||||||
- **Read an issue**: `glab issue view <number> --comments`. Use `-F json` for machine-readable output.
|
- **Read an issue**: `glab issue view <number> --comments`. Use `-F json` for machine-readable output.
|
||||||
- **List issues**: `glab issue list -F json` with appropriate `--label` filters.
|
- **List issues**: `glab issue list -O json` with appropriate `--label` filters.
|
||||||
- **Comment on an issue**: `glab issue note <number> --message "..."`. GitLab calls comments "notes".
|
- **Comment on an issue**: `glab issue note <number> --message "..."`. GitLab calls comments "notes".
|
||||||
- **Apply / remove labels**: `glab issue update <number> --label "..."` / `--unlabel "..."`. Multiple labels can be comma-separated or by repeating the flag.
|
- **Apply / remove labels**: `glab issue update <number> --label "..."` / `--unlabel "..."`. Multiple labels can be comma-separated or by repeating the flag.
|
||||||
- **Close**: `glab issue close <number>`. `glab issue close` does not accept a closing comment, so post the explanation first with `glab issue note <number> --message "..."`, then close.
|
- **Close**: `glab issue close <number>`. `glab issue close` does not accept a closing comment, so post the explanation first with `glab issue note <number> --message "..."`, then close.
|
||||||
@@ -41,6 +41,6 @@ Used by `/wayfinder`. The **map** is a single issue with **child** issues as tic
|
|||||||
- **Map**: a single issue labelled `wayfinder:map`, holding the Notes / Decisions-so-far / Fog body. `glab issue create --label wayfinder:map`. (On GitLab tiers with native epics, an epic may hold the map instead; a labelled issue works everywhere.)
|
- **Map**: a single issue labelled `wayfinder:map`, holding the Notes / Decisions-so-far / Fog body. `glab issue create --label wayfinder:map`. (On GitLab tiers with native epics, an epic may hold the map instead; a labelled issue works everywhere.)
|
||||||
- **Child ticket**: an issue carrying `Part of #<map>` at the top of its description and labels `wayfinder:<type>` (`research`/`prototype`/`grilling`/`task`). Once claimed, the ticket is assigned to the driving dev.
|
- **Child ticket**: an issue carrying `Part of #<map>` at the top of its description and labels `wayfinder:<type>` (`research`/`prototype`/`grilling`/`task`). Once claimed, the ticket is assigned to the driving dev.
|
||||||
- **Blocking**: GitLab's **native blocking link**, the canonical, UI-visible representation. Add it with the `/blocked_by #<n>` quick action, posted as a note (`glab issue note <child> --message "/blocked_by #<blocker>"`). Native blocking links are a Premium/Ultimate feature; on the free tier (or where unavailable) fall back to a `Blocked by: #<n>, #<n>` line at the top of the description. A ticket is unblocked when every blocker is closed.
|
- **Blocking**: GitLab's **native blocking link**, the canonical, UI-visible representation. Add it with the `/blocked_by #<n>` quick action, posted as a note (`glab issue note <child> --message "/blocked_by #<blocker>"`). Native blocking links are a Premium/Ultimate feature; on the free tier (or where unavailable) fall back to a `Blocked by: #<n>, #<n>` line at the top of the description. A ticket is unblocked when every blocker is closed.
|
||||||
- **Frontier query**: `glab issue list -F json` scoped to the map's children, drop any with an open blocker: a native `blocked_by` link to an open issue (`glab api projects/:id/issues/:iid/links`), or an open issue in the `Blocked by` line, or an assignee; first in map order wins.
|
- **Frontier query**: `glab issue list -O json` scoped to the map's children, drop any with an open blocker: a native `blocked_by` link to an open issue (`glab api projects/:id/issues/<child-iid>/links`), or an open issue in the `Blocked by` line, or an assignee; first in map order wins.
|
||||||
- **Claim**: `glab issue update <n> --assignee @me`, the session's first write.
|
- **Claim**: `glab issue update <n> --assignee @me`, the session's first write.
|
||||||
- **Resolve**: `glab issue note <n> --message "<answer>"`, then `glab issue close <n>`, then append a context pointer (gist + link) to the map's Decisions-so-far.
|
- **Resolve**: `glab issue note <n> --message "<answer>"`, then `glab issue close <n>`, then append a context pointer (gist + link) to the map's Decisions-so-far.
|
||||||
@@ -12,4 +12,4 @@ The skills speak in terms of five canonical triage roles. This file maps those r
|
|||||||
|
|
||||||
When a skill mentions a role (e.g. "apply the AFK-ready triage label"), use the corresponding label string from this table.
|
When a skill mentions a role (e.g. "apply the AFK-ready triage label"), use the corresponding label string from this table.
|
||||||
|
|
||||||
Edit the right-hand column to match whatever vocabulary you actually use.
|
Edit the "Label in our tracker" column to match whatever vocabulary you actually use.
|
||||||
+1
-1
@@ -21,7 +21,7 @@ A **seam** is the public boundary you test at: the interface where you observe b
|
|||||||
|
|
||||||
**Test only at pre-agreed seams.** Before writing any test, write down the seams under test and confirm them with the user. No test is written at an unconfirmed seam. You can't test everything, so agreeing the seams up front is how testing effort lands on the critical paths and complex logic instead of every edge case.
|
**Test only at pre-agreed seams.** Before writing any test, write down the seams under test and confirm them with the user. No test is written at an unconfirmed seam. You can't test everything, so agreeing the seams up front is how testing effort lands on the critical paths and complex logic instead of every edge case.
|
||||||
|
|
||||||
Ask: "What's the public interface, and which seams should we test?"
|
Ask: "What's the public interface, and which seams should we test?" Give each proposed seam a one-line note on what it catches and what it misses.
|
||||||
|
|
||||||
When the shape of that interface is itself in question (how deep the module is, where the seam belongs, what the interface should expose), call the Skill tool with "codebase-design" for the vocabulary. It is the shared source of the module, interface, depth, seam, adapter, leverage and locality terms, and it is a reference to consult, not a session to run.
|
When the shape of that interface is itself in question (how deep the module is, where the seam belongs, what the interface should expose), call the Skill tool with "codebase-design" for the vocabulary. It is the shared source of the module, interface, depth, seam, adapter, leverage and locality terms, and it is a reference to consult, not a session to run.
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -9,7 +9,7 @@ The user has asked you to teach them something. This is a stateful request - the
|
|||||||
|
|
||||||
## Teaching Workspace
|
## Teaching Workspace
|
||||||
|
|
||||||
Treat the current directory as a teaching workspace. The state of their learning is captured in this directory in several files:
|
Treat the current directory as a teaching workspace. Workspace paths (`./lessons/` and the rest) resolve from the directory `/teach` was run in; only the `*-FORMAT.md` links resolve from this skill's folder. The state of their learning is captured in this directory in several files:
|
||||||
|
|
||||||
- `MISSION.md`: A document capturing the _reason_ the user is interested in the topic. This should be used to ground all teaching. Use the format in [MISSION-FORMAT.md](./MISSION-FORMAT.md).
|
- `MISSION.md`: A document capturing the _reason_ the user is interested in the topic. This should be used to ground all teaching. Use the format in [MISSION-FORMAT.md](./MISSION-FORMAT.md).
|
||||||
- `./reference/*.html`: A directory of reference materials. These are the compressed learnings from the lessons - cheat sheets, reference algorithms, syntax, yoga poses, glossaries. They are the raw units of learning. They should be beautiful documents which print out well, and are designed for quick reference.
|
- `./reference/*.html`: A directory of reference materials. These are the compressed learnings from the lessons - cheat sheets, reference algorithms, syntax, yoga poses, glossaries. They are the raw units of learning. They should be beautiful documents which print out well, and are designed for quick reference.
|
||||||
@@ -107,7 +107,7 @@ For skill acquisition, difficulty is the tool. Effortful retrieval is what build
|
|||||||
|
|
||||||
Each of these should be based on a **feedback loop**, where the user receives feedback on their performance. This feedback loop should be as tight as possible, giving feedback immediately - and ideally automatically.
|
Each of these should be based on a **feedback loop**, where the user receives feedback on their performance. This feedback loop should be as tight as possible, giving feedback immediately - and ideally automatically.
|
||||||
|
|
||||||
For quizzes, each answer should be exactly the same number of words (and characters, if possible). Don't give the user any clues about the answer through formatting.
|
For quizzes, each answer should be exactly the same number of words (and characters, if possible). Vary which position holds the correct answer across questions. Don't give the user any clues about the answer through formatting or order.
|
||||||
|
|
||||||
## Acquiring Wisdom
|
## Acquiring Wisdom
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -60,7 +60,7 @@ Iterate until the user approves the breakdown.
|
|||||||
Publish the approved tickets. **How** depends on the tracker `/setup-matt-pocock-skills` configured; the tickets are the same either way, only the shape of the blocking edges changes:
|
Publish the approved tickets. **How** depends on the tracker `/setup-matt-pocock-skills` configured; the tickets are the same either way, only the shape of the blocking edges changes:
|
||||||
|
|
||||||
- **Local files** → write one file per ticket under `.scratch/<feature-slug>/issues/<NN>-<slug>.md`, numbered from `01` in dependency order (blockers first). Each file's "Blocked by" lists the numbers/titles it depends on. Use the per-ticket file template below: one ticket per file, never a single combined file.
|
- **Local files** → write one file per ticket under `.scratch/<feature-slug>/issues/<NN>-<slug>.md`, numbered from `01` in dependency order (blockers first). Each file's "Blocked by" lists the numbers/titles it depends on. Use the per-ticket file template below: one ticket per file, never a single combined file.
|
||||||
- **A real issue tracker (GitHub, Linear, …)** → publish one issue per ticket in dependency order (blockers first) so each ticket's blocking edges can reference real identifiers. Use the platform's native blocking / sub-issue relationship where it has one; otherwise set each ticket's "Blocked by" to the blocking issues. Apply the `ready-for-agent` triage label unless instructed otherwise; the tickets are agent-grabbable by construction.
|
- **A real issue tracker (GitHub, Linear, …)** → publish one issue per ticket in dependency order (blockers first) so each ticket's blocking edges can reference real identifiers. Use the platform's native blocking relationship where it has one; otherwise set each ticket's "Blocked by" to the blocking issues. If the source was an existing issue, make each ticket its sub-issue (tracker doc's operation). Apply the `ready-for-agent` triage label unless instructed otherwise; the tickets are agent-grabbable by construction.
|
||||||
|
|
||||||
Work the **frontier**: any ticket whose blockers are all done. For a purely linear chain that means top to bottom.
|
Work the **frontier**: any ticket whose blockers are all done. For a purely linear chain that means top to bottom.
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ The end-to-end behaviour this ticket makes work, from the user's perspective, no
|
|||||||
|
|
||||||
## Blocked by
|
## Blocked by
|
||||||
|
|
||||||
- A reference to each blocking ticket, or "None (can start immediately)".
|
- A reference to each blocking ticket, or "None (can start immediately)". Omit this section when blockers were set as native edges.
|
||||||
|
|
||||||
</issue-template>
|
</issue-template>
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -62,7 +62,7 @@ Each ticket is a **child issue** of the map; the tracker's issue id is its ident
|
|||||||
<the decision or investigation this ticket resolves>
|
<the decision or investigation this ticket resolves>
|
||||||
```
|
```
|
||||||
|
|
||||||
Each ticket carries a `wayfinder:<type>` label, one of `research`, `prototype`, `grilling`, `task` (see [Ticket Types](#ticket-types)).
|
Each ticket carries a `wayfinder:<type>` label, one of `research`, `prototype`, `grilling`, `task` (see [Ticket Types](#ticket-types)). `wayfinder:` labels are the only labels a map and its tickets carry, never a triage label like `ready-for-agent`: they are decisions, not implementation work.
|
||||||
|
|
||||||
A session **claims** a ticket by assigning it to the dev driving the map, **first**, before any work, so concurrent sessions skip it. That assignee _is_ the claim: an open, unassigned ticket is unclaimed.
|
A session **claims** a ticket by assigning it to the dev driving the map, **first**, before any work, so concurrent sessions skip it. That assignee _is_ the claim: an open, unassigned ticket is unclaimed.
|
||||||
|
|
||||||
@@ -111,8 +111,8 @@ User invokes with a loose idea.
|
|||||||
1. **Name the destination.** Call the Skill tool twice, for "grilling" and "domain-modeling", to pin down what this map is finding its way to: the spec, decision, or change. The destination fixes the scope, so it's settled first.
|
1. **Name the destination.** Call the Skill tool twice, for "grilling" and "domain-modeling", to pin down what this map is finding its way to: the spec, decision, or change. The destination fixes the scope, so it's settled first.
|
||||||
2. **Map the frontier.** Grill again, **breadth-first** this time: fan out across the whole space rather than deep on any one thread, surfacing the open decisions and the first steps takeable now. **If this surfaces no fog** (the way to the destination is already clear, the whole journey small enough for one session), you don't need a map. Stop and ask the user how they'd like to proceed.
|
2. **Map the frontier.** Grill again, **breadth-first** this time: fan out across the whole space rather than deep on any one thread, surfacing the open decisions and the first steps takeable now. **If this surfaces no fog** (the way to the destination is already clear, the whole journey small enough for one session), you don't need a map. Stop and ask the user how they'd like to proceed.
|
||||||
3. **Create the map** (label `wayfinder:map`): Destination and Notes filled in, Decisions-so-far empty, the fog sketched into **Not yet specified**.
|
3. **Create the map** (label `wayfinder:map`): Destination and Notes filled in, Decisions-so-far empty, the fog sketched into **Not yet specified**.
|
||||||
4. **Create the tickets you can specify now** as child issues of the map, then wire blocking edges in a **second pass** (issues need ids before they can reference each other). Wiring sorts them into the frontier and the blocked; everything you can't yet specify stays in the fog: the **Not yet specified** section.
|
4. **Create the tickets you can specify now** as child issues of the map, then wire blocking edges in a **second pass** (issues need ids before they can reference each other). Write cross-references in that pass too, with real ids: a placeholder `#<n>` auto-links to an unrelated issue. Wiring sorts them into the frontier and the blocked; everything you can't yet specify stays in the fog: the **Not yet specified** section.
|
||||||
5. **Fire the research subagents.** For each `research` ticket you just created, spin up a subagent that calls the Skill tool with "research" to resolve it in parallel, capturing its findings on a throwaway `research/<name>` branch with a context pointer from the ticket.
|
5. **Fire the research subagents.** For each `research` ticket you just created, spin up a subagent that calls the Skill tool with "research" to resolve it in parallel, capturing its findings on a throwaway `research/<name>` branch with a context pointer from the ticket. Push the branch but open no PR: it is never merged.
|
||||||
6. Stop: charting is one session's work; it hand-resolves nothing.
|
6. Stop: charting is one session's work; it hand-resolves nothing.
|
||||||
|
|
||||||
### Work through the map
|
### Work through the map
|
||||||
@@ -121,7 +121,7 @@ User invokes with a map (URL or number). A ticket is **optional**: without one,
|
|||||||
|
|
||||||
1. Load the **map**: the low-res view, not every ticket body.
|
1. Load the **map**: the low-res view, not every ticket body.
|
||||||
2. Choose the ticket. If the user named one, use it. Otherwise take the first frontier ticket in order. **Claim it**: assign it to yourself before any work.
|
2. Choose the ticket. If the user named one, use it. Otherwise take the first frontier ticket in order. **Claim it**: assign it to yourself before any work.
|
||||||
3. Resolve it. **Zoom as needed**: fetch the full body of any related or closed ticket on demand; call the Skill tool for whichever skills the `## Notes` block names. If in doubt, call the Skill tool twice, for "grilling" and "domain-modeling".
|
3. Resolve it as the type its `wayfinder:<type>` label names (see [Ticket Types](#ticket-types)). Read the label, not just the body: the body never states the type. **Zoom as needed**: fetch the full body of any related or closed ticket on demand; call the Skill tool for whichever skills the `## Notes` block names. If in doubt, call the Skill tool twice, for "grilling" and "domain-modeling".
|
||||||
4. Record the resolution: post the answer as a **resolution comment**, **close** the issue, and **append a context pointer** to the map's Decisions-so-far.
|
4. Record the resolution: post the answer as a **resolution comment**, **close** the issue, and **append a context pointer** to the map's Decisions-so-far.
|
||||||
5. Add newly-surfaced tickets (create-then-wire); graduate any fog the answer has made specifiable, clearing each graduated patch from **Not yet specified** so it lives only as its new ticket. If the answer reveals that a ticket (this one or another) sits beyond the destination, **rule it out of scope** rather than resolving it on the route. If the decision invalidates other parts of the map, update or delete those tickets.
|
5. Add newly-surfaced tickets (create-then-wire); graduate any fog the answer has made specifiable, clearing each graduated patch from **Not yet specified** so it lives only as its new ticket. If the answer reveals that a ticket (this one or another) sits beyond the destination, **rule it out of scope** rather than resolving it on the route. If the decision invalidates other parts of the map, update or delete those tickets.
|
||||||
|
|
||||||
|
|||||||
+25
-20
@@ -70,7 +70,7 @@ open_url() {
|
|||||||
elif command -v explorer.exe >/dev/null 2>&1; then explorer.exe "$url"
|
elif command -v explorer.exe >/dev/null 2>&1; then explorer.exe "$url"
|
||||||
elif command -v xdg-open >/dev/null 2>&1; then xdg-open "$url"
|
elif command -v xdg-open >/dev/null 2>&1; then xdg-open "$url"
|
||||||
elif command -v open >/dev/null 2>&1; then open "$url"
|
elif command -v open >/dev/null 2>&1; then open "$url"
|
||||||
else warn "couldn't open a browser; visit it manually: $url"; fi
|
else false; fi
|
||||||
} >/dev/null 2>&1 || warn "couldn't open a browser, so visit it manually: $url"
|
} >/dev/null 2>&1 || warn "couldn't open a browser, so visit it manually: $url"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,52 +88,57 @@ confirm() {
|
|||||||
[[ "$reply" =~ ^[Yy] ]]
|
[[ "$reply" =~ ^[Yy] ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
# _existing KEY: current value of KEY in ENV_FILE, if any.
|
# _existing KEY: current value of KEY in ENV_FILE, if any, with write_env's quoting undone.
|
||||||
_existing() {
|
_existing() {
|
||||||
[[ -f "$ENV_FILE" ]] || return 1
|
[[ -f "$ENV_FILE" ]] || return 1
|
||||||
local line; line=$(grep -E "^${1}=" "$ENV_FILE" | tail -n1) || return 1
|
local line value sq="'\\''"; line=$(grep -E "^${1}=" "$ENV_FILE" | tail -n1) || return 1
|
||||||
printf '%s' "${line#*=}"
|
value="${line#*=}"
|
||||||
|
if [[ "$value" == \'*\' ]]; then value="${value:1:${#value}-2}"; value="${value//"$sq"/\'}"; fi
|
||||||
|
printf '%s' "$value"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ask KEY "Prompt" reads a value into $KEY. Offers the existing .env value as
|
# ask KEY "Prompt" reads a value into $KEY. Offers the existing .env value as
|
||||||
# a default on re-runs (Enter keeps it). Visible input (non-secret).
|
# a default on re-runs (Enter keeps it). Visible input (non-secret), edited
|
||||||
|
# with Readline. Fails at EOF with no input.
|
||||||
ask() {
|
ask() {
|
||||||
local key="$1" prompt="$2" current input
|
local key="$1" prompt="$2" current input s=$'\001' e=$'\002' p rc=0
|
||||||
current=$(_existing "$key" || true)
|
current=$(_existing "$key" || true)
|
||||||
if [[ -n "$current" ]]; then
|
# Readline needs the colour codes wrapped in \001..\002 to place the cursor.
|
||||||
printf ' %s%s%s %s[Enter keeps current]%s ' "$BOLD" "$prompt" "$RESET" "$DIM" "$RESET"
|
p=" $s$BOLD$e$prompt$s$RESET$e "
|
||||||
else
|
[[ -n "$current" ]] && p+="$s$DIM$e[Enter keeps current]$s$RESET$e "
|
||||||
printf ' %s%s%s ' "$BOLD" "$prompt" "$RESET"
|
read -e -r -p "$p" input || rc=1
|
||||||
fi
|
if (( rc )) && [[ -z "$input" ]]; then return 1; fi
|
||||||
read -r input || true
|
|
||||||
[[ -z "$input" && -n "$current" ]] && input="$current"
|
[[ -z "$input" && -n "$current" ]] && input="$current"
|
||||||
printf -v "$key" '%s' "$input"
|
printf -v "$key" '%s' "$input"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ask_secret KEY "Prompt" is like ask, but input is hidden.
|
# ask_secret KEY "Prompt" is like ask, but input is hidden.
|
||||||
ask_secret() {
|
ask_secret() {
|
||||||
local key="$1" prompt="$2" current input
|
local key="$1" prompt="$2" current input rc=0
|
||||||
current=$(_existing "$key" || true)
|
current=$(_existing "$key" || true)
|
||||||
if [[ -n "$current" ]]; then
|
if [[ -n "$current" ]]; then
|
||||||
printf ' %s%s%s %s[Enter keeps current]%s ' "$BOLD" "$prompt" "$RESET" "$DIM" "$RESET"
|
printf ' %s%s%s %s[Enter keeps current]%s ' "$BOLD" "$prompt" "$RESET" "$DIM" "$RESET"
|
||||||
else
|
else
|
||||||
printf ' %s%s%s ' "$BOLD" "$prompt" "$RESET"
|
printf ' %s%s%s ' "$BOLD" "$prompt" "$RESET"
|
||||||
fi
|
fi
|
||||||
read -rs input || true
|
read -rs input || rc=1
|
||||||
printf '\n'
|
printf '\n'
|
||||||
|
if (( rc )) && [[ -z "$input" ]]; then return 1; fi
|
||||||
[[ -z "$input" && -n "$current" ]] && input="$current"
|
[[ -z "$input" && -n "$current" ]] && input="$current"
|
||||||
printf -v "$key" '%s' "$input"
|
printf -v "$key" '%s' "$input"
|
||||||
}
|
}
|
||||||
|
|
||||||
# write_env KEY VALUE upserts KEY=VALUE into ENV_FILE (creates it; replaces
|
# write_env KEY VALUE upserts KEY='VALUE' into ENV_FILE (replaces any existing
|
||||||
# any existing line). Idempotent.
|
# line). Idempotent. Creates ENV_FILE at mode 0600; writes through a symlink
|
||||||
|
# and keeps an existing file's mode.
|
||||||
write_env() {
|
write_env() {
|
||||||
local key="$1" value="$2" tmp
|
local key="$1" value="$2" tmp sq="'\\''"
|
||||||
touch "$ENV_FILE"
|
[[ -e "$ENV_FILE" ]] || (umask 077 && : > "$ENV_FILE")
|
||||||
tmp=$(mktemp)
|
tmp=$(mktemp)
|
||||||
grep -vE "^${key}=" "$ENV_FILE" > "$tmp" || true
|
grep -vE "^${key}=" "$ENV_FILE" > "$tmp" || true
|
||||||
printf '%s=%s\n' "$key" "$value" >> "$tmp"
|
printf "%s='%s'\n" "$key" "${value//\'/$sq}" >> "$tmp"
|
||||||
mv "$tmp" "$ENV_FILE"
|
cat "$tmp" > "$ENV_FILE"
|
||||||
|
rm -f "$tmp"
|
||||||
WRITTEN_ENV+=("$key")
|
WRITTEN_ENV+=("$key")
|
||||||
printf ' %s✓ wrote%s %s → %s\n' "$GREEN" "$RESET" "$key" "$ENV_FILE"
|
printf ' %s✓ wrote%s %s → %s\n' "$GREEN" "$RESET" "$key" "$ENV_FILE"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user