@hegemonart/get-design-done 1.28.8 → 1.30.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +116 -0
- package/README.de.md +25 -0
- package/README.fr.md +25 -0
- package/README.it.md +25 -0
- package/README.ja.md +25 -0
- package/README.ko.md +25 -0
- package/README.md +30 -0
- package/README.zh-CN.md +25 -0
- package/SKILL.md +2 -0
- package/agents/design-authority-watcher.md +42 -1
- package/agents/design-reflector.md +50 -0
- package/package.json +1 -1
- package/reference/capability-gap-stage-gate.md +261 -0
- package/reference/known-failure-modes.md +521 -0
- package/reference/pseudonymization-rules.md +189 -0
- package/reference/registry.json +22 -1
- package/reference/schemas/events.schema.json +158 -3
- package/reference/schemas/generated.d.ts +319 -4
- package/scripts/cli/gdd-events.mjs +35 -2
- package/scripts/gsd-cleanup-incubator.cjs +367 -0
- package/scripts/lib/apply-reflections/incubator-proposals.cjs +455 -0
- package/scripts/lib/authority-watcher/index.cjs +201 -0
- package/scripts/lib/bandit-router.cjs +92 -9
- package/scripts/lib/failure-mode-matcher.cjs +460 -0
- package/scripts/lib/gsd-health-mirror/index.cjs +37 -1
- package/scripts/lib/incubator-author.cjs +845 -0
- package/scripts/lib/install/interactive.cjs +27 -2
- package/scripts/lib/issue-reporter/cli-flag-report.cjs +153 -0
- package/scripts/lib/issue-reporter/consent-prompt.cjs +231 -0
- package/scripts/lib/issue-reporter/dedup.cjs +458 -0
- package/scripts/lib/issue-reporter/destination.cjs +37 -0
- package/scripts/lib/issue-reporter/draft-writer.cjs +157 -0
- package/scripts/lib/issue-reporter/gh-absent-fallback.cjs +220 -0
- package/scripts/lib/issue-reporter/gh-submit.cjs +114 -0
- package/scripts/lib/issue-reporter/kill-switch.cjs +122 -0
- package/scripts/lib/issue-reporter/payload-assembly.cjs +367 -0
- package/scripts/lib/issue-reporter/privacy-diff.cjs +385 -0
- package/scripts/lib/issue-reporter/report-flow.cjs +269 -0
- package/scripts/lib/issue-reporter/triage-matcher.cjs +270 -0
- package/scripts/lib/pseudonymize.cjs +444 -0
- package/scripts/lib/reflections-cycle-writer.cjs +172 -0
- package/scripts/lib/reflector/capability-gap-scan.cjs +751 -0
- package/scripts/lib/reflector-capability-gap-aggregator.cjs +352 -0
- package/scripts/lib/reflector-kfm-proposer.cjs +468 -0
- package/scripts/release-smoke-test.cjs +33 -2
- package/scripts/validate-incubator-scope.cjs +133 -0
- package/skills/apply-reflections/SKILL.md +20 -1
- package/skills/apply-reflections/apply-reflections-procedure.md +106 -4
- package/skills/fast/SKILL.md +46 -0
- package/skills/reflect/SKILL.md +9 -0
- package/skills/reflect/procedures/capability-gap-scan.md +120 -0
- package/skills/report-issue/SKILL.md +53 -0
- package/skills/report-issue/report-issue-procedure.md +120 -0
- package/skills/router/SKILL.md +5 -0
- package/skills/router/capability-gap-emitter.md +65 -0
- package/skills/update/SKILL.md +3 -2
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: apply-reflections-procedure
|
|
3
3
|
type: heuristic
|
|
4
|
-
version: 1.
|
|
5
|
-
phase:
|
|
6
|
-
tags: [apply-reflections, proposal, frontmatter, reference, budget, question, global-skill]
|
|
7
|
-
last_updated: 2026-05-
|
|
4
|
+
version: 1.3.0
|
|
5
|
+
phase: 30.5
|
|
6
|
+
tags: [apply-reflections, proposal, frontmatter, reference, budget, question, global-skill, incubator, kfm-candidate]
|
|
7
|
+
last_updated: 2026-05-21
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# Apply-Reflections — Per-Type Procedure
|
|
@@ -66,3 +66,105 @@ the proposal's bracketed type tag.
|
|
|
66
66
|
|
|
67
67
|
5. Print: "Global skill written to ~/.claude/gdd/global-skills/<name>.md — auto-loads in all future gdd sessions"
|
|
68
68
|
6. Append `**Applied**: <date>` to proposal in reflections file
|
|
69
|
+
|
|
70
|
+
### [INCUBATOR]
|
|
71
|
+
|
|
72
|
+
Incubator drafts come from `scripts/lib/incubator-author.cjs` (Phase 29-04). They live at
|
|
73
|
+
`.design/reflections/incubator/<slug>/` and contain `manifest.json` + `DRAFT.md` + (optional) `ORIGIN.md`.
|
|
74
|
+
|
|
75
|
+
Use `scripts/lib/apply-reflections/incubator-proposals.cjs` for all actions.
|
|
76
|
+
|
|
77
|
+
**Discovery + render** (once per cycle):
|
|
78
|
+
|
|
79
|
+
1. Call `discoverIncubatorDrafts()` → `Array<Draft>`. Skip malformed entries silently (already warned on stderr by the helper).
|
|
80
|
+
2. For each draft: call `renderProposal(draft)` and print the returned markdown block. The user sees a header (slug + kind), a diff vs the nearest existing artifact (or "net-new"), an Origin section listing capability-gap signals, and the full draft body.
|
|
81
|
+
3. Prompt: `(a) accept (r) reject (d) defer (e) edit (q) quit`.
|
|
82
|
+
|
|
83
|
+
**Per-action behavior:**
|
|
84
|
+
|
|
85
|
+
1. **accept** — call `applyAccept(draft, { registryPath, repoRoot })`.
|
|
86
|
+
- The helper calls `validateScope(draft.target_path)` from `scripts/validate-incubator-scope.cjs` **before** any write. Out-of-scope paths throw and the registry stays untouched. This is the non-bypassable scope guard (D-05).
|
|
87
|
+
- On success: target artifact written, `reference/registry.json` appended with `{ slug, path, added, origin: 'incubator' }`, incubator subdir removed last (T-29.05-04 — partial-failure leaves draft retryable).
|
|
88
|
+
- Print: "Accepted — promoted to <target_path>; registered."
|
|
89
|
+
- Append `**Applied**: <date>` to the proposal block.
|
|
90
|
+
|
|
91
|
+
2. **reject** — call `applyReject(draft)`. Only the incubator subdir is removed; registry is untouched. Append `**Reviewed: rejected**` to the reflections file.
|
|
92
|
+
|
|
93
|
+
3. **defer** — no-op. Print "Deferred — draft re-surfaces next run." Append `**Reviewed: deferred**`.
|
|
94
|
+
|
|
95
|
+
4. **edit** — call `applyEdit(draft)` (uses `$EDITOR` or the `editorCmd` array option). On clean exit, the helper reloads the draft and the caller re-runs `renderProposal` + the prompt. On non-zero exit, the original draft is preserved unchanged.
|
|
96
|
+
|
|
97
|
+
**Stage-1 gate (D-01 — no auto-flip):**
|
|
98
|
+
|
|
99
|
+
1. At the start of the cycle, call `checkStage1Gate({ gateSpecPath, statePath, registryPath })`. The call is **read-only** — never mutates state. The returned `{ thresholdMet, summary, optInRecorded }` is informational.
|
|
100
|
+
2. If `thresholdMet && !optInRecorded`, surface a one-time prompt:
|
|
101
|
+
```
|
|
102
|
+
Stage-1 capability-gap authoring threshold met: <summary>
|
|
103
|
+
Enable incubator-draft promotion? (y/N)
|
|
104
|
+
```
|
|
105
|
+
3. **Only on explicit `y`**, call `recordOptIn({ statePath, confirmedBy })`. The function is idempotent — a second call detects the existing record and returns `{ alreadyRecorded: true }`. Never call it on any other input.
|
|
106
|
+
|
|
107
|
+
**Why this is gated.** The `[INCUBATOR]` proposal class can write executable surface (agents + skills) into the plugin runtime. Both Phase 29 D-01 (no auto-flip) and D-05 (scope guard) exist because that surface has integration-test and security implications that exceed reflector autonomy. `validateScope` keeps the file landing zone confined to `agents/<slug>.md` or `skills/<slug>/SKILL.md`. The Stage-1 gate keeps the *whether* of opting in to incubator authoring under explicit user control even after the data threshold says we have enough signal.
|
|
108
|
+
|
|
109
|
+
**Bandit-fairness gate on `accept` (Phase 29 Plan 06 / CONTEXT D-04).**
|
|
110
|
+
|
|
111
|
+
When the `accept` action promotes an incubator draft, the bandit-router arms for the freshly-promoted agent/skill MUST be bootstrapped with `prior_class: 'promoted_incubator'`. This invokes a conservative `Beta(2, 8)` bootstrap prior (posterior mean 0.2) instead of the optimistic Phase 23.5 informed prior — the bandit-fairness gate IS the staging mechanism (D-04: no separate two-step ratify split). The conservative prior suppresses preferential selection until ~8-10 successful pulls accumulate.
|
|
112
|
+
|
|
113
|
+
Call shape (whether eagerly invoked on promotion or via first-pull lazy bootstrap):
|
|
114
|
+
|
|
115
|
+
```javascript
|
|
116
|
+
const bandit = require('./scripts/lib/bandit-router.cjs');
|
|
117
|
+
// Per arm bootstrapped for the freshly-promoted agent:
|
|
118
|
+
bandit.update({
|
|
119
|
+
agent: '<promoted-slug>',
|
|
120
|
+
bin: '<touches-bin>',
|
|
121
|
+
tier: '<chosen-tier>',
|
|
122
|
+
reward: <bernoulli>,
|
|
123
|
+
prior_class: 'promoted_incubator', // Phase 29 Plan 06 / D-04 — Beta(2,8) staging
|
|
124
|
+
});
|
|
125
|
+
// Or for the delegate-aware case (Plan 27-07):
|
|
126
|
+
bandit.updateWithDelegate({
|
|
127
|
+
agent: '<promoted-slug>',
|
|
128
|
+
bin: '<touches-bin>',
|
|
129
|
+
tier: '<chosen-tier>',
|
|
130
|
+
delegate: '<peer-cli-or-none>',
|
|
131
|
+
reward: <bernoulli>,
|
|
132
|
+
prior_class: 'promoted_incubator',
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Omitting `prior_class` reverts to Phase 23.5 informed-prior bootstrap (non-breaking). The reward math is unchanged — `prior_class` only affects bootstrap.
|
|
137
|
+
|
|
138
|
+
### [KFM-CANDIDATE]
|
|
139
|
+
|
|
140
|
+
Known-failure-mode catalogue proposals come from `scripts/lib/reflector-kfm-proposer.cjs` (Phase 30.5-03 D-05). They live at `.design/reflections/incubator/kfm-<slug>/CATALOGUE-ENTRY.md` and contain a single fenced ```yaml block pre-filled with the Phase 30.5 schema-v2 11-field shape (`id` + `pattern` + `diagnosis` + `remedy` + `severity` + `propose_report` + `symptom` + `root_cause` + `fix` + `related_phases` + `first_observed_cycle`). Two of those — `pattern` and `fix` — are `TODO:` placeholders the reflector cannot infer; the user fills them via the **edit** action before accepting.
|
|
141
|
+
|
|
142
|
+
Two upstream signals share this draft surface (D-06):
|
|
143
|
+
- `capability_gap` clusters of size ≥3 with no existing-entry match (Phase 29-03 aggregator + `failure-mode-matcher.match()`).
|
|
144
|
+
- `kfm-candidate` events from the Phase 30.5-03 Task 2 authority-watcher whitelist (D-06 — single events bypass the ≥3 gate).
|
|
145
|
+
|
|
146
|
+
Use `scripts/lib/reflector-kfm-proposer.cjs` for all actions:
|
|
147
|
+
|
|
148
|
+
**Discovery + render** (once per cycle):
|
|
149
|
+
|
|
150
|
+
1. Glob `.design/reflections/incubator/kfm-*/CATALOGUE-ENTRY.md` → list pending KFM drafts.
|
|
151
|
+
2. For each draft: read the body, show the origin header (source, parent event ids OR article url) + the proposed yaml block.
|
|
152
|
+
3. Prompt: `(a) accept (r) reject (d) defer (e) edit (q) quit`.
|
|
153
|
+
|
|
154
|
+
**Per-action behavior:**
|
|
155
|
+
|
|
156
|
+
1. **accept** — call `applyAccept(draftPath, { repoRoot })`.
|
|
157
|
+
- The helper re-stamps the proposed `id` with the next available `KFM-NNN` from the catalogue (avoids collisions when multiple drafts promote in the same run).
|
|
158
|
+
- Appends a `### KFM-NNN — <symptom heading>` section into `reference/known-failure-modes.md` with the yaml block intact.
|
|
159
|
+
- Appends a `reference/registry.json` entry: `{ name: 'known-failure-modes/kfm-NNN', path: 'reference/known-failure-modes.md', type: 'failure-mode', phase: 30.5, origin: 'incubator-kfm', added: '<ISO date>' }`.
|
|
160
|
+
- Removes the incubator subdir LAST (partial-failure leaves the draft retryable).
|
|
161
|
+
- Print: "Accepted — promoted to KFM-NNN in reference/known-failure-modes.md."
|
|
162
|
+
- Append `**Applied**: <date>` to the proposal entry (when surfaced from a reflections file).
|
|
163
|
+
|
|
164
|
+
2. **reject** — call `applyReject(draftPath)`. Only the incubator subdir is removed; catalogue + registry untouched. Print: "Rejected — draft removed."
|
|
165
|
+
|
|
166
|
+
3. **defer** — call `applyDefer(draftPath, { deferredUntil })` where `deferredUntil` is an ISO date (default: today + 30d). The helper stamps `deferred_until: <ISO>` into the draft body. Print: "Deferred — draft re-surfaces next run."
|
|
167
|
+
|
|
168
|
+
4. **edit** — call `applyEdit(draftPath)` which returns the draft path. The caller opens `$EDITOR` on the path; on clean exit, re-discover the draft and re-prompt. Typical edits: replace `pattern: 'TODO: ...'` with a conservative regex, replace `fix: 'TODO: ...'` with a step-by-step user-runnable remedy, set `severity` if `medium` default is wrong.
|
|
169
|
+
|
|
170
|
+
**Why this is gated.** `reference/known-failure-modes.md` feeds Phase 30's `triage-matcher.cjs` BEFORE the consent prompt — a bad entry could mute legitimate issue reports. The user-review gate is non-negotiable (D-05). The proposer is strictly proposal-only; the canonical catalogue only changes via the accept action.
|
package/skills/fast/SKILL.md
CHANGED
|
@@ -41,5 +41,51 @@ The leanest possible execution path. No subagents, no STATE.md update, no DESIGN
|
|
|
41
41
|
- No STATE.md mutation.
|
|
42
42
|
- No pipeline stage invocation.
|
|
43
43
|
- Do not proceed if the change turns out to be non-trivial — bail out and recommend `/gdd:quick` or the full pipeline.
|
|
44
|
+
- Do not skip the `capability_gap` emit on bail-out — Stage-0 telemetry depends on it (Phase 29 D-01).
|
|
45
|
+
|
|
46
|
+
## Emitting capability_gap on no-skill-match
|
|
47
|
+
|
|
48
|
+
If step 2 cannot locate any candidate files for the task description (or all candidates are filtered out as off-topic), or if the change in step 4 turns out to be non-trivial in a way that has no obvious resolution without a dedicated skill/agent, emit ONE `capability_gap` event before returning control to the user. This feeds Phase 29 Stage-0 telemetry — the reflector pattern-detection pass (Plan 29-02) and aggregation (Plan 29-03) read these events from the chain file (`.design/gep/events.jsonl`) to surface recurring capability gaps in `/gdd:apply-reflections`.
|
|
49
|
+
|
|
50
|
+
Synchronous emitter call (via Bash):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
node -e '
|
|
54
|
+
const { appendChainEvent } = require("./scripts/lib/event-chain.cjs");
|
|
55
|
+
const { createHash, randomUUID } = require("node:crypto");
|
|
56
|
+
const intent = process.env.GDD_INTENT || "";
|
|
57
|
+
const payload = {
|
|
58
|
+
event_id: randomUUID(),
|
|
59
|
+
parent_event_id: null,
|
|
60
|
+
source: "fast",
|
|
61
|
+
context_hash: createHash("sha256").update(intent).digest("hex"),
|
|
62
|
+
intent_summary: intent.slice(0, 256),
|
|
63
|
+
suggested_kind: "skill",
|
|
64
|
+
evidence_refs: [],
|
|
65
|
+
};
|
|
66
|
+
appendChainEvent({
|
|
67
|
+
agent: "fast",
|
|
68
|
+
outcome: "capability_gap",
|
|
69
|
+
payload,
|
|
70
|
+
type: "capability_gap",
|
|
71
|
+
timestamp: new Date().toISOString(),
|
|
72
|
+
sessionId: process.env.GDD_SESSION_ID || "fast-cli",
|
|
73
|
+
});
|
|
74
|
+
'
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Notes:
|
|
78
|
+
- `evidence_refs` is empty `[]` for fast (no trajectory in `/gdd:fast` — that path is too lean by design).
|
|
79
|
+
- `parent_event_id` is null (root event for the fast bail-out).
|
|
80
|
+
- `suggested_kind` is `"skill"` because fast bail-outs are usually narrow primitives, not multi-step workflows. Plan 29-03's aggregator may upgrade to `"agent"` if a `context_hash` cluster shows multi-step usage.
|
|
81
|
+
- The emitter MUST NOT block — `appendChainEvent` already swallows IO errors via its existing try/catch (see `scripts/lib/event-chain.cjs:97-105`).
|
|
82
|
+
- The 7-field payload is preserved verbatim through `appendChainEvent`'s opaque-extras pattern; the chain row carries `type`, `timestamp`, `sessionId`, `payload` as opaque caller-supplied fields and is projected back to the events-schema envelope shape by readers (Plan 29-03 aggregator).
|
|
83
|
+
|
|
84
|
+
Trigger conditions:
|
|
85
|
+
- **Trigger 1**: Step 2 returns zero candidate files for the task description (target is genuinely missing).
|
|
86
|
+
- **Trigger 2**: Step 2 returns more than 2 candidates AND the user bail-out in step 2 fires (stop and ask).
|
|
87
|
+
- **Trigger 3**: Step 4 reveals the change is non-trivial in a way that has no obvious primitive resolution (the `## Do Not` "Do not proceed" line fires).
|
|
88
|
+
|
|
89
|
+
MCP-probe failures (connection down, transport-layer errors) do NOT emit `capability_gap` — those are Phase 22 connection-status concerns (D-08).
|
|
44
90
|
|
|
45
91
|
## FAST COMPLETE
|
package/skills/reflect/SKILL.md
CHANGED
|
@@ -33,6 +33,14 @@ Run `design-reflector` on demand against the current (or specified) cycle. Produ
|
|
|
33
33
|
```
|
|
34
34
|
Use the resolved slug where `<slug>` appears.
|
|
35
35
|
|
|
36
|
+
3.5. **Optional: capability-gap pre-scan** (Phase 29 Plan 02; runs only if `.design/config.json.reflector.capability_gap_scan_enabled !== false`):
|
|
37
|
+
See @skills/reflect/procedures/capability-gap-scan.md for the full procedure.
|
|
38
|
+
The `design-reflector` agent runs the scan automatically as part of its reflection pass; this step lets users dry-run it independently with:
|
|
39
|
+
```
|
|
40
|
+
node scripts/lib/reflector/capability-gap-scan.cjs --dry-run
|
|
41
|
+
```
|
|
42
|
+
The scan emits `capability_gap` events (`source: "reflector_pattern"`) for recurring patterns lacking a dedicated executable owner; Plan 29-03 aggregates these for `/gdd:apply-reflections`.
|
|
43
|
+
|
|
36
44
|
4. **Spawn design-reflector**:
|
|
37
45
|
```
|
|
38
46
|
Task("design-reflector", """
|
|
@@ -74,3 +82,4 @@ Run `design-reflector` on demand against the current (or specified) cycle. Produ
|
|
|
74
82
|
- Do not auto-apply any proposal.
|
|
75
83
|
- Do not modify agent files, reference files, or budget.json.
|
|
76
84
|
- Do not run the full audit pipeline — this is a standalone reflection run.
|
|
85
|
+
- Do not bypass the threshold knob. The default `reflector.capability_gap_threshold: 3` exists to suppress noise; do NOT lower it below 1.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Procedure: reflector pattern-detection capability-gap scan
|
|
2
|
+
|
|
3
|
+
Run during the reflection pass to detect recurring patterns lacking a dedicated executable owner; emit `capability_gap` events with `source: "reflector_pattern"` (Phase 29 Plan 02) for Plan 29-03 to aggregate.
|
|
4
|
+
|
|
5
|
+
## Inputs
|
|
6
|
+
|
|
7
|
+
Three signal sources are scanned. All paths are repo-relative; the scan tolerates absent sources (returns `[]` from that source, no error):
|
|
8
|
+
|
|
9
|
+
- `.design/intel/*.md` — Phase 11 intel-store slice files. The scan extracts `Touches:` lines, tokenizes the comma-separated value, and clusters slices that share the same canonical `(sortedTouches, agent_type)` signal.
|
|
10
|
+
- `.design/telemetry/posterior.json` — Phase 23.5 bandit posterior file written by `scripts/lib/bandit-router.cjs`. The scan reads `arms[]` and flags arms whose `count >= threshold` AND whose `agent` is in `GENERIC_AGENT_FALLBACKS` (`general-purpose`, `default-executor`, `fallback`, `generic`) OR is not in the project's specialized-agent set.
|
|
11
|
+
- `.design/gep/events.jsonl` — Phase 22 typed-causal event chain via `scripts/lib/event-chain.cjs`. The scan filters rows to the last `windowDays` (default 30), groups by `(sortedDecisionRefs, agent)`, and flags sequences that recur ≥ threshold times.
|
|
12
|
+
|
|
13
|
+
## Outputs
|
|
14
|
+
|
|
15
|
+
For each qualifying cluster:
|
|
16
|
+
|
|
17
|
+
- One `capability_gap` event appended to `.design/gep/events.jsonl` via `appendChainEvent` from `scripts/lib/event-chain.cjs`.
|
|
18
|
+
- The event's `payload` carries exactly the 7 fields documented in CONTEXT D-02: `event_id` (UUIDv4), `parent_event_id` (nullable), `source` (`"reflector_pattern"`), `context_hash` (sha256 of normalized signal), `intent_summary` (≤256 chars), `suggested_kind` (`"agent"` | `"skill"`), `evidence_refs[]` (TrajectoryRef pointers with `sha256:` content hash).
|
|
19
|
+
- The orchestrator returns `{findings, emittedEventIds, skippedBelowThreshold}` to the caller (the `design-reflector` agent), which cites the event IDs in its run summary under `## Capability gaps emitted`.
|
|
20
|
+
|
|
21
|
+
## Algorithm
|
|
22
|
+
|
|
23
|
+
The orchestrator `runCapabilityGapScan(opts)` in `scripts/lib/reflector/capability-gap-scan.cjs` follows four phases:
|
|
24
|
+
|
|
25
|
+
1. **Load threshold.** Resolve in priority order: `opts.threshold` → `.design/config.json` field `reflector.capability_gap_threshold` → `DEFAULT_THRESHOLD` (=3). Throws if the resolved value is non-integer or < 1.
|
|
26
|
+
2. **Build agent inventory.** Read `agents/*.md` frontmatter `name:` fields; partition into specialized vs generic.
|
|
27
|
+
3. **Three scans (parallel-safe in concept; sequential in code for simplicity).** Call `scanIntelTouchesClusters`, `scanPosteriorArms`, `scanTrajectorySlices` and concatenate findings.
|
|
28
|
+
4. **Emit.** For each finding, call the injected `opts.emit` spy (tests) or the default emitter (`defaultEmitCapabilityGapEvent`, which calls `appendChainEvent` from `scripts/lib/event-chain.cjs`). Collect returned `event_id`s.
|
|
29
|
+
|
|
30
|
+
Key entry points (all exported from the module):
|
|
31
|
+
|
|
32
|
+
- `computeContextHash({touches, agent_type})` — pure deterministic hash (sha256 of normalized JSON; touches are sorted ASCII-asc).
|
|
33
|
+
- `scanIntelTouchesClusters({intelDir, existingAgents, threshold, baseDir})`
|
|
34
|
+
- `scanPosteriorArms({posteriorPath, specializedAgents, threshold, baseDir})`
|
|
35
|
+
- `scanTrajectorySlices({chainPath, windowDays, threshold, specializedAgents, baseDir})`
|
|
36
|
+
- `runCapabilityGapScan(opts)` — orchestrator.
|
|
37
|
+
|
|
38
|
+
The `context_hash` is the join key for Plan 29-03's aggregation: the same signal across runs produces the same hash regardless of touches-list ordering.
|
|
39
|
+
|
|
40
|
+
## Threshold configuration
|
|
41
|
+
|
|
42
|
+
The scan honors `reflector.capability_gap_threshold` in `.design/config.json`. Default `N = 3` (must be integer ≥ 1). Override knob documented in `.design/config.example.json`.
|
|
43
|
+
|
|
44
|
+
To raise the threshold (suppress noise):
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"reflector": {
|
|
49
|
+
"capability_gap_threshold": 5
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
To dry-run with an injected threshold without writing the config file:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
node -e "console.log(JSON.stringify(require('./scripts/lib/reflector/capability-gap-scan.cjs').runCapabilityGapScan({threshold: 5, emit: () => 'DRY'}), null, 2))"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Hard exclusions (D-08)
|
|
61
|
+
|
|
62
|
+
MCP-probe failures MUST NOT contribute to any scan. Connection observability is Phase 22's separate surface; mixing those signals into the capability-gap stream would pollute Plan 29-03's clustering.
|
|
63
|
+
|
|
64
|
+
The scan filters rows matching ANY of the following three shapes (liberal exclusion):
|
|
65
|
+
|
|
66
|
+
- `outcome === 'connection-error'`
|
|
67
|
+
- `agent === 'mcp-probe'`
|
|
68
|
+
- `mcp_probe: true`
|
|
69
|
+
|
|
70
|
+
The exclusion is enforced in `scanTrajectorySlices` via the internal `isMcpProbeRow` predicate (also exported for tests). Tests assert all three exclusion shapes regress separately.
|
|
71
|
+
|
|
72
|
+
## Evidence-refs contract (D-07)
|
|
73
|
+
|
|
74
|
+
Each event's `evidence_refs[]` is a list of POINTERS to source slices, never duplicated content. The scan emits items in the schema's `TrajectoryRef` shape:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
interface TrajectoryRef {
|
|
78
|
+
trajectory_path: string; // repo-relative path
|
|
79
|
+
byte_start: number; // inclusive byte offset
|
|
80
|
+
byte_end: number; // exclusive byte offset
|
|
81
|
+
content_hash: string; // "sha256:" + 64-hex
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Internally, the scan carries a line-based `{path, lineStart, lineEnd, sha256}` shape on each `Finding.evidence_refs`. The `lineRefToTrajectoryRef` translator converts to the schema shape at emit time. The sha256 algorithm: read lines `[lineStart..lineEnd]` (1-based inclusive), join with `'\n'` (no trailing newline — stable across OSes), sha256 the UTF-8 bytes.
|
|
86
|
+
|
|
87
|
+
Consumers (Plan 29-03 + audit tooling) re-read the slice and recompute the hash; mismatch = chain mutation; abort + warn.
|
|
88
|
+
|
|
89
|
+
## CLI dry-run
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
node scripts/lib/reflector/capability-gap-scan.cjs --dry-run
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Runs all three scans against the current working directory and prints findings (and would-be event payloads) without writing to `.design/gep/events.jsonl`. Useful for operator-side inspection of what the design-reflector agent would emit on the next reflection pass.
|
|
96
|
+
|
|
97
|
+
Without `--dry-run`, the CLI writes events to the chain file and prints a one-line summary: `emitted N capability_gap event(s); skipped M below threshold`.
|
|
98
|
+
|
|
99
|
+
## Testing
|
|
100
|
+
|
|
101
|
+
Tests live at `tests/reflector-capability-gap.test.cjs` and run on synthetic in-tmpdir fixtures only (D-11 — no live writes to real `.design/`). Each test passes an injected `emit` spy so no real `appendChainEvent` calls occur. The hash-pin mutation-detection regression is enforced by a dedicated test: re-read the pointed-to slice, recompute the sha256, and assert mismatch after the source file is mutated.
|
|
102
|
+
|
|
103
|
+
Run the tests directly:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
node --test tests/reflector-capability-gap.test.cjs
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Run the full suite to verify no regressions in `tests/reflection-proposal.test.cjs` or `tests/reflector-cross-runtime.test.cjs`:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npm test
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## See also
|
|
116
|
+
|
|
117
|
+
- `reference/schemas/events.schema.json` — the `capability_gap` event class shipped by Plan 29-01 (the 7-field shape + `TrajectoryRef` definition).
|
|
118
|
+
- `scripts/lib/event-chain.cjs` — `appendChainEvent` (the real emitter API; 29-01 did NOT ship a separate helper file).
|
|
119
|
+
- `scripts/lib/bandit-router.cjs` — Phase 23.5 posterior file producer (the source for scan #2).
|
|
120
|
+
- `.planning/phases/29-capability-gap-self-authoring/CONTEXT.md` — phase decisions (D-02 7-field shape, D-07 hash-pinning, D-08 MCP carve-out, D-11 tmpdir tests).
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: report-issue
|
|
3
|
+
description: "Consent-gated GitHub issue reporter for /gdd. Triages locally against `reference/known-failure-modes.md`, layers Phase 22 secret-redaction with Phase 30 pseudonymization, drafts the payload to `.design/issue-drafts/`, opens `$EDITOR` for inspection, and submits via the user's `gh` CLI to a hardcoded repo only after explicit per-issue consent. Use when the user runs `/gdd:report-issue`, hits a failure on a whitelisted command with the `--report` flag, or asks to file a bug against get-design-done."
|
|
4
|
+
argument-hint: "[<command-name>] [--force-report]"
|
|
5
|
+
tools: Read, Write, Grep, Bash
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# /gdd:report-issue
|
|
9
|
+
|
|
10
|
+
Local-first, consent-gated GitHub issue reporter. No auto-mode. Destination repo is hardcoded. The full step-by-step lives in [report-issue-procedure.md](./report-issue-procedure.md); this file is the entry contract.
|
|
11
|
+
|
|
12
|
+
## Pre-flight
|
|
13
|
+
|
|
14
|
+
If invoked without an error context (the user just typed `/gdd:report-issue` cold), ask in one round-trip: *"What command failed and what was the error?"* Capture command name + a paraphrase of the error. Don't interrogate — one short answer is enough to feed the triage matcher.
|
|
15
|
+
|
|
16
|
+
## Steps
|
|
17
|
+
|
|
18
|
+
0. **Kill-switch** (D-08). Call `isDisabled()` from `scripts/lib/issue-reporter/kill-switch.cjs` FIRST. Either env (`GDD_DISABLE_ISSUE_REPORTER=1`) OR config (`.design/config.json` with `{ "issue_reporter": false }`) makes the command unavailable. Surface the disable line via `getDisableReason()` ('env' wins when both set) and stop. No draft, no triage, no payload. `gsd-health` mirrors the same line for at-a-glance verification.
|
|
19
|
+
1. **Triage**. Call `matchKnownFailure(errorContext)` from `scripts/lib/issue-reporter/triage-matcher.cjs` (Plan 30-03). If `matched === true` and `--force-report` is NOT set: print the suggested diagnosis + remedy, stop. Do NOT write a draft. (D-07)
|
|
20
|
+
2. **Assemble**. Call `assemble(commandName, errorContext, trajectoryRef?, capabilityGapEvent?)` from `scripts/lib/issue-reporter/payload-assembly.cjs` (Plan 30-02). This layers Phase 22 redact → Phase 30 pseudonymize, computes the fingerprint, and renders bilingual disclaimer + sections.
|
|
21
|
+
3. **Draft**. Call `writeDraft({title, body, fingerprint})` from `scripts/lib/issue-reporter/draft-writer.cjs`. Print the absolute path: `Draft written to .design/issue-drafts/<timestamp>-<fp8>.md`. The file persists across decline — the user keeps their work either way. (D-04)
|
|
22
|
+
4. **Dedup** (D-06). Call `searchByFingerprint(fingerprint, {destination})` from `scripts/lib/issue-reporter/dedup.cjs`. If `degraded === true` show a one-line warning and fall through to Step 5. If `matches.length === 0` fall through to Step 5 unchanged. If `matches.length >= 1` render the dedup UI listing each `{number,title,url}` with three actions per match: **`+1`** → `react(n, {destination})` (no new issue, D-06), **`me-too`** → `commentMeToo(n, {destination, errorContext, runtime, pluginVersion})` body is EXACTLY 3 fields (last error line, runtime, plugin version) from the ALREADY-pseudonymized 30-02 pipeline (D-01), **`new`** → fall through to Step 5 with the prepared draft despite the match.
|
|
23
|
+
5. **Edit**. If `$EDITOR` is set, the consent prompt opens it on the draft and blocks until exit. Otherwise the path is printed and the user can open it manually. `EDITOR` is the only env var the report flow reads.
|
|
24
|
+
6. **Consent**. Call `promptConsent({draftPath})` from `scripts/lib/issue-reporter/consent-prompt.cjs`. Re-reads the (possibly edited) draft from disk, prints a summary, asks `Submit this issue to hegemonart/get-design-done? [y/N]`. Anything other than `y`/`yes` declines. (D-03)
|
|
25
|
+
- Throws if `process.stdin.isTTY` is false (no auto-mode).
|
|
26
|
+
- Throws if any `process.env` key matching `/REPORT|ISSUE|AUTO_REPORT/i` is set to a truthy value.
|
|
27
|
+
7. **Submit**. On `y`, call `submitViaGh({title, body})` from `scripts/lib/issue-reporter/gh-submit.cjs`. This spawns `gh issue create --repo hegemonart/get-design-done --title <title> --body-file <tmp>`. Destination is hardcoded; no env var or flag can redirect it. (D-02 + D-05)
|
|
28
|
+
8. **gh-absent fallback** (D-10). If `detectGh()` from `scripts/lib/issue-reporter/gh-absent-fallback.cjs` returns false BEFORE Step 7 runs: call `runFallback(consent.finalBody)` to copy the (already-pseudonymized) payload to the clipboard via the platform's clipboard command (`pbcopy` on macOS, `wl-copy`/`xclip` on Linux, `clip.exe` on Windows) and print the message `gh CLI not found; payload copied to clipboard, paste into the link below.` followed by the issue-template URL. The draft persists on disk for re-submission once `gh` is installed.
|
|
29
|
+
|
|
30
|
+
## Use through `--report`
|
|
31
|
+
|
|
32
|
+
Whitelisted to specific commands via `scripts/lib/issue-reporter/cli-flag-report.cjs`. Only `gdd:plan-phase`, `gdd:execute-phase`, and `gdd:report-issue` itself install the flag — and only when `reference/known-failure-modes.md` has at least one `propose_report: true` entry. Non-whitelisted commands silently do not see the flag. (D-11)
|
|
33
|
+
|
|
34
|
+
## Use through `--force-report`
|
|
35
|
+
|
|
36
|
+
Available on `/gdd:report-issue`. Overrides the triage gate (Step 1) but does NOT override consent. The user still gets the draft + consent prompt. There is no path to submit without an explicit `y`.
|
|
37
|
+
|
|
38
|
+
## Privacy contract
|
|
39
|
+
|
|
40
|
+
- **Pseudonymization, not anonymization.** Disclaimer (Russian + English) is rendered at the top of the payload by Plan 30-02. Side-channel content (writing style, code patterns) may still re-identify. The user reviews the exact bytes before consenting.
|
|
41
|
+
- **No auto-mode, no env-var bypass.** Static tests (`tests/report-issue-no-auto-submit-static.test.cjs`) fail the build if anyone adds a `process.env.*REPORT*`/`*ISSUE*`/`*AUTO_REPORT*` read here or under `scripts/lib/issue-reporter/`. Runtime check in `consent-prompt.cjs` is the second layer.
|
|
42
|
+
- **Hardcoded destination.** `scripts/lib/issue-reporter/destination.cjs` is the only file that contains the literal repo string; it exports a frozen object so runtime tampering throws. Static tests enforce both invariants.
|
|
43
|
+
- **No HTTPS, no fetch.** Submission goes through the user's `gh` CLI only. Plan 30-07 ships a CI gate that fails the build if any network primitive is added here.
|
|
44
|
+
|
|
45
|
+
## Troubleshooting
|
|
46
|
+
|
|
47
|
+
- **`gh` not authenticated.** The submission step fails with a clear error pointing to the draft path. Run `gh auth login` and re-invoke `/gdd:report-issue` — the draft survives.
|
|
48
|
+
- **No `EDITOR` set.** The path is printed; open it in whatever editor you prefer, save, then return to the consent prompt.
|
|
49
|
+
- **Triage matched something irrelevant.** Pass `--force-report` to bypass the gate. Consent is still required.
|
|
50
|
+
- **`gh` not installed (D-10).** After consent, the payload is copied to your clipboard and an issue-template URL is printed. Paste into the URL to file manually. Install `gh` later and the existing draft can be re-submitted.
|
|
51
|
+
- **Command unavailable / disabled (D-08).** Run `gsd-health`. The `issue reporter:` line shows the active disable surface — either `disabled by env (GDD_DISABLE_ISSUE_REPORTER=1)` or `disabled by config (.design/config.json: issue_reporter=false)`. Unset the env var or flip the config key to re-enable.
|
|
52
|
+
|
|
53
|
+
See [report-issue-procedure.md](./report-issue-procedure.md) for the full procedure with rationale per decision (D-02, D-03, D-04, D-05, D-07, D-11).
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# `/gdd:report-issue` — Full Procedure
|
|
2
|
+
|
|
3
|
+
Long-form companion to [SKILL.md](./SKILL.md). Phase 28.5 keeps SKILL.md ≤100 lines; step-by-step + rationale live here.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
The report flow is the only outbound path the plugin offers. Every byte leaving the user's machine via this surface:
|
|
8
|
+
|
|
9
|
+
1. has been redacted for secrets (Phase 22 `redact.cjs`),
|
|
10
|
+
2. has been pseudonymized for identity (Plan 30-01 `pseudonymize.cjs`),
|
|
11
|
+
3. has been written to disk where the user can read it,
|
|
12
|
+
4. has cleared a pre-submit dedup check against the destination repo (Plan 30-05) — `+1` and `me-too` on a matching existing issue NEVER spawn a duplicate (D-06),
|
|
13
|
+
5. has been read back from disk after the user closed the editor, and
|
|
14
|
+
6. has cleared an explicit per-issue `y/N` prompt.
|
|
15
|
+
|
|
16
|
+
No environment variable, command-line flag, or build configuration bypasses any of these steps. Two test layers enforce this:
|
|
17
|
+
|
|
18
|
+
- **Static** (`tests/report-issue-destination-static.test.cjs`, `tests/report-issue-no-auto-submit-static.test.cjs`) — fail the build on any forbidden code pattern in `skills/report-issue/` or `scripts/lib/issue-reporter/`.
|
|
19
|
+
- **Runtime** (`tests/report-issue.test.cjs`) — 26 cases proving the orchestrator threads consent + persistence + edit-before-submit + triage + hardcoded destination + flag whitelist.
|
|
20
|
+
|
|
21
|
+
## Steps
|
|
22
|
+
|
|
23
|
+
### Step 1 — Triage gate (D-07)
|
|
24
|
+
|
|
25
|
+
`matchKnownFailure(errorContext)` regex-matches `error.message + error.stack` against `reference/known-failure-modes.md`. If matched, prints diagnosis + remedy and exits without writing a draft. `--force-report` overrides the gate but does NOT override consent.
|
|
26
|
+
|
|
27
|
+
### Step 2 — Assemble payload (D-01)
|
|
28
|
+
|
|
29
|
+
`assemble(commandName, errorContext, trajectoryRef?, capabilityGapEvent?)` returns markdown. Order is locked: redact → pseudonymize (Case 9 of 30-02 enforces). Bilingual disclaimer at top: "Это псевдонимизация, не анонимизация" / "This is pseudonymization, not anonymization." Fingerprint computed on the scrubbed stack so the same bug from different cwd's hashes the same.
|
|
30
|
+
|
|
31
|
+
### Step 3 — Write draft (D-04)
|
|
32
|
+
|
|
33
|
+
`writeDraft({title, body, fingerprint})` writes to `.design/issue-drafts/<YYYYMMDDTHHMMSSZ>-<fp8>.md`. The file has a small HTML-comment header (timestamp, destination, full fingerprint) so a future maintainer with a corrupted-looking draft can reconstruct provenance. The file is NOT deleted on decline — the user keeps their work.
|
|
34
|
+
|
|
35
|
+
### Step 4 — Pre-submit dedup (D-06)
|
|
36
|
+
|
|
37
|
+
`scripts/lib/issue-reporter/dedup.cjs` exports `searchByFingerprint(fingerprint, {destination})` which spawns `gh issue list --search "fingerprint:<hash>" --json number,title,url --repo <destination>` (read-only). Resolves `{matches: [...], degraded?, reason?}` — NEVER throws on gh failure.
|
|
38
|
+
|
|
39
|
+
Routing:
|
|
40
|
+
|
|
41
|
+
- `matches.length === 0` (no existing issue) **or** `degraded === true` (search unavailable; surface a one-line warning) → fall through to Step 5 with the prepared draft.
|
|
42
|
+
- `matches.length >= 1` → render the dedup UI listing each `{number, title, url}` with three actions per match:
|
|
43
|
+
|
|
44
|
+
- **`+1`** → `react(n, {destination})` spawns `gh api -X POST /repos/<destination>/issues/<n>/reactions -f content=+1`. Resolves `{ok:true}`; exits the report flow on success ("reaction recorded on #<n>"). **NO new issue is created** (D-06).
|
|
45
|
+
- **`me-too`** → `commentMeToo(n, {destination, errorContext, runtime, pluginVersion})` spawns `gh issue comment <n> --repo <destination> --body <body>`. The body contains EXACTLY three fields (`Last error:`, `Runtime:`, `Plugin version:`) — nothing more (negative-presence tested). `errorContext.lastErrorLine` is the ALREADY-pseudonymized last error line from 30-02's payload pipeline (D-01); dedup.cjs does NOT re-derive raw stderr. Exits the report flow on success ("comment added to #<n>"). **NO new issue is created** (D-06).
|
|
46
|
+
- **`new`** → fall through to Step 5 with the prepared draft despite the match (user explicitly opted to force a new issue).
|
|
47
|
+
|
|
48
|
+
`+1` and `me-too` failures (auth/network/rate) propagate as rejected promises with annotated `.reason` so the caller can offer retry/cancel — they do NOT silently fall back to creating a new issue (that would defeat dedup intent).
|
|
49
|
+
|
|
50
|
+
Wiring: `runReportFlow` calls `options.dedupCheck({fingerprint, title})` BEFORE the consent prompt (`report-flow.cjs` STEP 4). The skill drives the `+1`/`me-too`/`new` UI by passing a `dedupCheck` callback that wraps `searchByFingerprint` + the `react`/`commentMeToo` calls. Returning truthy `existing` from the callback short-circuits `runReportFlow` to `{submitted:false, reason:'duplicate'}`. Returning falsy continues to Step 5.
|
|
51
|
+
|
|
52
|
+
### Step 5 — Edit (optional)
|
|
53
|
+
|
|
54
|
+
If `$EDITOR` is set, `promptConsent` spawns it on the draft path and blocks until exit. Otherwise the user opens it manually. `EDITOR` is a POSIX convention (git, crontab, gh all use it); the static-grep test only forbids env-var reads matching `/REPORT|ISSUE|AUTO_REPORT/i`.
|
|
55
|
+
|
|
56
|
+
### Step 6 — Consent prompt (D-03)
|
|
57
|
+
|
|
58
|
+
The single submission gate for the new-issue path. Three preconditions must hold:
|
|
59
|
+
|
|
60
|
+
1. `process.stdin.isTTY === true`.
|
|
61
|
+
2. No env var matches `/REPORT|ISSUE|AUTO_REPORT/i` with a truthy value (`rejectBypassEnv` throws otherwise, naming the offender).
|
|
62
|
+
3. The draft file exists and is readable.
|
|
63
|
+
|
|
64
|
+
The function prints a summary (destination, draft path, title, first 10 body lines), asks `Submit this issue to hegemonart/get-design-done? [y/N]` via `readline`, treats anything other than `y`/`yes` (case-insensitive, trimmed) as decline, and **re-reads the draft from disk** so user edits in Step 5 are picked up.
|
|
65
|
+
|
|
66
|
+
(The `+1` / `me-too` paths from Step 4 do NOT pass through this consent prompt — selecting either action in the dedup UI IS the explicit consent for that minimal interaction. The new-issue path always passes through this prompt.)
|
|
67
|
+
|
|
68
|
+
### Step 7 — Submit via `gh` (D-05 + D-02)
|
|
69
|
+
|
|
70
|
+
`submitViaGh({title, body})` spawns:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
gh issue create --repo hegemonart/get-design-done --title <title> --body-file <tmp/body.md>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Body is written to a tmp file to avoid arg-length and shell-escaping. URL parsed from stdout. No HTTPS, no fetch, no third-party packages — only the user's `gh` CLI with their credentials.
|
|
77
|
+
|
|
78
|
+
## The `--report` flag (D-11)
|
|
79
|
+
|
|
80
|
+
`cli-flag-report.cjs` whitelists three commands today: `gdd:plan-phase`, `gdd:execute-phase`, `gdd:report-issue`. The whitelist intersects with `listProposeReportModes()`; if the catalogue has zero `propose_report: true` entries, the flag disables everywhere. `installReportFlagOn(parser, commandName)` is a no-op for non-whitelisted commands; `parseReportFlag` returns `{report: false}` regardless of argv.
|
|
81
|
+
|
|
82
|
+
## Privacy guarantees
|
|
83
|
+
|
|
84
|
+
| Layer | Guarantee | Enforced by |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| Static code | No `process.env.*REPORT*`/`*ISSUE*`/`*AUTO_REPORT*` reads in the report tree | `report-issue-no-auto-submit-static.test.cjs` |
|
|
87
|
+
| Static string | Only `destination.cjs` may contain the literal repo string | `report-issue-destination-static.test.cjs` |
|
|
88
|
+
| Static flag | No `--yes` / `--no-confirm` / `--auto-confirm` / `--auto-submit` strings | `report-issue-no-auto-submit-static.test.cjs` |
|
|
89
|
+
| Runtime frozen | `DESTINATION_REPO` reassignment throws | static test S3 + behavioural H2 |
|
|
90
|
+
| Runtime env | `rejectBypassEnv` throws on truthy forbidden env var | `report-issue.test.cjs` B1, U1 |
|
|
91
|
+
| Runtime TTY | `promptConsent` throws on `!stdin.isTTY` | C5 |
|
|
92
|
+
| Runtime consent | Only `y`/`yes` accepted | C1..C3, U3 |
|
|
93
|
+
| Runtime re-read | `promptConsent` re-reads draft before returning final body | E1, E2 |
|
|
94
|
+
| Runtime destination | `submitViaGh` always passes `--repo hegemonart/get-design-done` | H1 |
|
|
95
|
+
| Dedup destination | `dedup.cjs` accepts `destination` only as a parameter — no env/config lookup (D-02) | `issue-reporter-dedup.test.cjs` test 11 |
|
|
96
|
+
| Dedup body shape | `buildMeTooBody` returns EXACTLY 3 lines (`Last error:` / `Runtime:` / `Plugin version:`) — no stack/path/env/cmd (D-06) | tests 5 + 6 (verbatim + negative-presence) |
|
|
97
|
+
| Dedup network | `dedup.cjs` imports only `child_process`; no outbound URL literals, no global fetch primitive, no third-party HTTP client libraries (D-05) | 30-07 static-grep gate |
|
|
98
|
+
| Dedup test hermeticity | No live `gh` calls in CI; injected spawn spy + traced `child_process.spawnSync` counter assert 0 real invocations (D-13) | `issue-reporter-dedup.test.cjs` test 10 |
|
|
99
|
+
| Dedup pseudonymization | `me-too` body uses the ALREADY-pseudonymized `errorContext.lastErrorLine` from 30-02's pipeline; dedup.cjs does NOT re-derive raw stderr (D-01) | dedup test "commentMeToo passes pseudonymized lastErrorLine through to gh body" |
|
|
100
|
+
|
|
101
|
+
## Troubleshooting
|
|
102
|
+
|
|
103
|
+
- **`gh` not authenticated**: submission throws with status + stderr; draft path preserved. Run `gh auth login`, retry. (T-30-04-08 accepted)
|
|
104
|
+
- **`EDITOR` spawns wrong tool**: set `EDITOR=<your-editor>` in shell rc.
|
|
105
|
+
- **Triage matched something irrelevant**: pass `--force-report`. Consent still required.
|
|
106
|
+
- **TTY refused (CI / non-interactive)**: by design — run locally. (T-30-04-05 mitigated)
|
|
107
|
+
- **No `--report` flag on a command you expected**: not on the whitelist; file an issue via this flow describing the use case.
|
|
108
|
+
|
|
109
|
+
## Forward-looking hooks
|
|
110
|
+
|
|
111
|
+
- **Plan 30-05** *(landed)* — `scripts/lib/issue-reporter/dedup.cjs` wires `options.dedupCheck` to `gh issue list --search "fingerprint:<hash>"`. The skill drives the `+1` / `me-too` / `new` UI by passing a `dedupCheck` callback that wraps `searchByFingerprint` + `react` + `commentMeToo`. The hook in `runReportFlow` now runs BEFORE consent (per D-06).
|
|
112
|
+
- **Plan 30-06** *(landed)* — `scripts/lib/issue-reporter/kill-switch.cjs` adds the D-08 dual-surface disable check (`GDD_DISABLE_ISSUE_REPORTER=1` env OR `.design/config.json: { "issue_reporter": false }` config). `runReportFlow` calls `isDisabled()` as **Step 0**, BEFORE triage and any draft writing — when disabled, returns `{ submitted:false, reason:'disabled', surface:'env'|'config', message }`. `scripts/lib/issue-reporter/gh-absent-fallback.cjs` adds the D-10 clipboard+URL path (pbcopy / wl-copy → xclip / clip.exe) — invoked after consent when `detectGh()` returns false. `scripts/lib/gsd-health-mirror/index.cjs` mirrors the disable surface as a 5th health check; output is exactly one of: `issue reporter: enabled` / `issue reporter: disabled by env (GDD_DISABLE_ISSUE_REPORTER=1)` / `issue reporter: disabled by config (.design/config.json: issue_reporter=false)`. Tests at `tests/issue-reporter-fallback.test.cjs` (15 cases). Env wins precedence when both flags are set (matches gsd-health display).
|
|
113
|
+
- **Plan 30-07** ships the network-isolation CI gate. Plans 30-04 and 30-05 already meet the invariant (no outbound URL literals, no global fetch primitive, no third-party HTTP client libraries — see `tests/issue-reporter-network-isolation.test.cjs` for the enforced forbidden-token list); the gate locks it in.
|
|
114
|
+
|
|
115
|
+
## References
|
|
116
|
+
|
|
117
|
+
- [SKILL.md](./SKILL.md) — entry contract.
|
|
118
|
+
- `reference/pseudonymization-rules.md` — full R1..R8 rule catalog (Plan 30-01).
|
|
119
|
+
- `reference/known-failure-modes.md` — triage catalogue (Plan 30-03).
|
|
120
|
+
- `.planning/phases/30-issue-reporter/CONTEXT.md` — phase decisions D-01..D-15.
|
package/skills/router/SKILL.md
CHANGED
|
@@ -75,6 +75,11 @@ Every `/gdd:*` SKILL.md's first substantive step is: spawn the router via `Task`
|
|
|
75
75
|
|
|
76
76
|
If `.design/budget.json` is missing, assume defaults from `reference/config-schema.md` per D-12. If `reference/model-prices.md` is missing, emit `estimated_cost_usd: null` and log a warning — do not block.
|
|
77
77
|
|
|
78
|
+
## Emitting capability_gap on unmatched intent
|
|
79
|
+
|
|
80
|
+
When the router cannot resolve `intent-string` to a known agent (no `description` match, no `default-tier` rule, no path-selection fallback), emit ONE `capability_gap` event with `source: "router"` before returning the conservative-fallback JSON. Feeds Phase 29 Stage-0 telemetry — see `./capability-gap-emitter.md` for the synchronous Node snippet, semantic notes (suggested_kind = `"agent"`, MCP-probe exclusion per D-08, back-compat invariant on router output), and the opaque-extras payload routing through `appendChainEvent`.
|
|
81
|
+
|
|
78
82
|
## Non-Goals
|
|
79
83
|
|
|
80
84
|
The router does not: (a) make a model call, (b) write files, (c) enforce budget caps (that's the hook's job), (d) learn from history (Phase 11 reflector territory per D-07).
|
|
85
|
+
The router does not author capability-gap CLUSTERS — Stage-0 emit is single-event-per-failure. Aggregation across events is Plan 29-03's reflector pass.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# gdd-router — capability_gap emitter (Phase 29 / D-02 / D-08)
|
|
2
|
+
|
|
3
|
+
Co-located reference for `skills/router/SKILL.md` — split out per Phase 28.5
|
|
4
|
+
contract (router SKILL ≤100 lines) and the Phase 28.6 co-location pattern.
|
|
5
|
+
|
|
6
|
+
## When to emit
|
|
7
|
+
|
|
8
|
+
If the router cannot resolve `intent-string` to a known agent (no agent's
|
|
9
|
+
`description` field matches, no `default-tier` rule applies, and the fallback
|
|
10
|
+
path-selection table returns nothing meaningful), emit ONE `capability_gap`
|
|
11
|
+
event before returning the conservative-fallback JSON output to the caller.
|
|
12
|
+
|
|
13
|
+
This feeds Phase 29 Stage-0 telemetry — the reflector pattern-detection pass
|
|
14
|
+
(Plan 29-02) and aggregation (Plan 29-03) read these events from the chain
|
|
15
|
+
file (`.design/gep/events.jsonl`) to surface recurring router-unmatched
|
|
16
|
+
intents as candidate agents in `/gdd:apply-reflections`.
|
|
17
|
+
|
|
18
|
+
## Synchronous emitter snippet
|
|
19
|
+
|
|
20
|
+
Same shape as fast, with `source: "router"`:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
node -e '
|
|
24
|
+
const { appendChainEvent } = require("./scripts/lib/event-chain.cjs");
|
|
25
|
+
const { createHash, randomUUID } = require("node:crypto");
|
|
26
|
+
const intent = process.env.GDD_INTENT || "";
|
|
27
|
+
const payload = {
|
|
28
|
+
event_id: randomUUID(),
|
|
29
|
+
parent_event_id: null,
|
|
30
|
+
source: "router",
|
|
31
|
+
context_hash: createHash("sha256").update(intent).digest("hex"),
|
|
32
|
+
intent_summary: intent.slice(0, 256),
|
|
33
|
+
suggested_kind: "agent",
|
|
34
|
+
evidence_refs: [],
|
|
35
|
+
};
|
|
36
|
+
appendChainEvent({
|
|
37
|
+
agent: "router",
|
|
38
|
+
outcome: "capability_gap",
|
|
39
|
+
payload,
|
|
40
|
+
type: "capability_gap",
|
|
41
|
+
timestamp: new Date().toISOString(),
|
|
42
|
+
sessionId: process.env.GDD_SESSION_ID || "router-cli",
|
|
43
|
+
});
|
|
44
|
+
'
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Notes
|
|
48
|
+
|
|
49
|
+
- `suggested_kind` is `"agent"` because router unmatched intents typically
|
|
50
|
+
describe multi-step workflows (the unit the router resolves).
|
|
51
|
+
- Router-unmatched is NOT the same as MCP-probe failure (per D-08). If
|
|
52
|
+
gdd-router returns a fallback because a peer-CLI connection is down, do
|
|
53
|
+
NOT emit capability_gap — that's a Phase 22 connection-status concern.
|
|
54
|
+
- The emitter is the LAST step before returning the fallback JSON. Router
|
|
55
|
+
output is unchanged (back-compat per the existing `## Output schema
|
|
56
|
+
versioning` table in `SKILL.md`); the event is a SIDE EFFECT, not a
|
|
57
|
+
payload addition.
|
|
58
|
+
- Router output JSON contract is UNCHANGED — back-compat preserved.
|
|
59
|
+
- The 7-field payload flows through `appendChainEvent`'s opaque-extras
|
|
60
|
+
pattern verbatim; the chain row carries `type`, `timestamp`, `sessionId`,
|
|
61
|
+
`payload` as opaque extras and is projected back to the events-schema
|
|
62
|
+
envelope by Plan 29-03 aggregation.
|
|
63
|
+
|
|
64
|
+
MCP-probe failures (connection down, transport-layer errors) do NOT emit
|
|
65
|
+
`capability_gap` — those are Phase 22 connection-status concerns (D-08).
|
package/skills/update/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gdd-update
|
|
3
3
|
description: "Update get-design-done to the latest release. Preserves .design/config.json and ./.claude/skills/."
|
|
4
|
-
argument-hint: "[--dry-run] [--version <tag>]"
|
|
4
|
+
argument-hint: "[--dry-run] [--version <tag>] [--show-privacy-diff]"
|
|
5
5
|
tools: Read, Write, Bash
|
|
6
6
|
disable-model-invocation: true
|
|
7
7
|
---
|
|
@@ -14,9 +14,10 @@ Updates the `get-design-done` plugin to the latest release (or a specific tag),
|
|
|
14
14
|
|
|
15
15
|
1. **Pre-flight check** — read current version from `.claude-plugin/plugin.json` (fallback: `plugin.json` at project root). Print current version and, when available, the latest release tag from GitHub.
|
|
16
16
|
2. **Dry-run** — if `--dry-run` is passed, print the planned steps and exit without making changes.
|
|
17
|
-
3. **Backup** — read `.design/config.json` into memory. Snapshot the file list under `./.claude/skills/` so we can detect collisions later.
|
|
17
|
+
3. **Backup** — read `.design/config.json` into memory. Snapshot the file list under `./.claude/skills/` so we can detect collisions later. **Additionally, snapshot the text contents of `scripts/lib/pseudonymize.cjs`, `scripts/lib/issue-reporter/payload-assembly.cjs`, and `scripts/lib/issue-reporter/destination.cjs` to a tempdir (mirroring the same relative paths under that tempdir's root). This is the OLD tree used by the privacy-diff step below. Also read the previous-version string from `.design/privacy-diff-last-version.txt` if it exists, into a variable `prevVersion` (null if the file is absent).** (Plan 30-07 D-09)
|
|
18
18
|
4. **Pull update** — run `claude plugin install hegemonart/get-design-done` (or `claude plugin install hegemonart/get-design-done@<tag>` when `--version <tag>` is passed). This re-syncs all plugin files from the release.
|
|
19
19
|
5. **Restore config** — write `.design/config.json` back from the in-memory backup. The installer may reset the config to defaults; this step guarantees user settings survive.
|
|
20
|
+
5.5. **Privacy diff** — `const pd = require('./scripts/lib/issue-reporter/privacy-diff.cjs');` Compute `showDiff = pd.shouldAutoShow(prevVersion, currentVersion, oldTreeTempdir, repoRoot) || flags.showPrivacyDiff`. If `showDiff` is true: call `pd.computePrivacyDiff(oldTreeTempdir, repoRoot)`, pass the result to `pd.renderPrivacyDiff`, and print the returned markdown to stdout under a clear "## Privacy-critical changes" banner. If `--show-privacy-diff` was passed but `prevVersion === null` (first-ever upgrade), print: "Privacy diff requested but no previous version is recorded. Snapshot file `.design/privacy-diff-last-version.txt` will be written now; the next upgrade will be able to diff against this version." After printing (or skipping), write `currentVersion` to `.design/privacy-diff-last-version.txt` so the next upgrade compares against THIS version. (Plan 30-07 D-09)
|
|
20
21
|
6. **Preserve local skills** — `./.claude/skills/` is project-local and outside the plugin tree. Re-list the directory and confirm none of the pre-update files disappeared. Warn loudly if any did.
|
|
21
22
|
7. **Post-update advisory** — print:
|
|
22
23
|
|