@opengsd/gsd-core 1.7.0-rc.6 → 1.7.0
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 +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/agents/gsd-executor.md +2 -1
- package/agents/gsd-security-auditor.md +13 -15
- package/bin/install.js +5 -4
- package/commands/gsd/mempalace-capture.md +27 -1
- package/commands/gsd/surface.md +6 -6
- package/gsd-core/bin/gsd-tools.cjs +63 -2
- package/gsd-core/bin/lib/api-coverage.cjs +3 -4
- package/gsd-core/bin/lib/audit.cjs +7 -6
- package/gsd-core/bin/lib/capability-registry.cjs +57 -57
- package/gsd-core/bin/lib/check-command-router.cjs +1 -1
- package/gsd-core/bin/lib/clock.cjs +19 -0
- package/gsd-core/bin/lib/commands.cjs +48 -9
- package/gsd-core/bin/lib/config-loader.cjs +6 -2
- package/gsd-core/bin/lib/config.cjs +12 -0
- package/gsd-core/bin/lib/core-utils.cjs +8 -2
- package/gsd-core/bin/lib/drift.cjs +4 -4
- package/gsd-core/bin/lib/frontmatter.cjs +22 -0
- package/gsd-core/bin/lib/gsd2-import.cjs +2 -1
- package/gsd-core/bin/lib/init.cjs +27 -13
- package/gsd-core/bin/lib/install-engine.cjs +3 -2
- package/gsd-core/bin/lib/installer-migration-authoring.cjs +2 -1
- package/gsd-core/bin/lib/installer-migrations.cjs +1 -1
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +342 -0
- package/gsd-core/bin/lib/markdown-table.cjs +698 -0
- package/gsd-core/bin/lib/milestone.cjs +217 -31
- package/gsd-core/bin/lib/phase-command-router.cjs +50 -2
- package/gsd-core/bin/lib/phase-lifecycle.cjs +62 -36
- package/gsd-core/bin/lib/phase-locator.cjs +23 -2
- package/gsd-core/bin/lib/phase.cjs +436 -61
- package/gsd-core/bin/lib/plan-scan.cjs +3 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +218 -13
- package/gsd-core/bin/lib/roadmap.cjs +100 -49
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +7 -6
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +3 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +2 -1
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +25 -17
- package/gsd-core/bin/lib/schema-detect.cjs +2 -1
- package/gsd-core/bin/lib/security.cjs +1 -1
- package/gsd-core/bin/lib/shell-command-projection.cjs +61 -25
- package/gsd-core/bin/lib/smart-entry.cjs +4 -3
- package/gsd-core/bin/lib/state-transition.cjs +100 -45
- package/gsd-core/bin/lib/state.cjs +391 -126
- package/gsd-core/bin/lib/surface.cjs +2 -2
- package/gsd-core/bin/lib/template.cjs +2 -1
- package/gsd-core/bin/lib/uat.cjs +54 -8
- package/gsd-core/bin/lib/ui-safety-gate.cjs +23 -1
- package/gsd-core/bin/lib/verify.cjs +4 -3
- package/gsd-core/bin/lib/workstream.cjs +3 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +1 -1
- package/gsd-core/bin/lib/write-set.cjs +38 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +2 -0
- package/gsd-core/references/checkpoints.md +12 -0
- package/gsd-core/workflows/add-tests.md +1 -1
- package/gsd-core/workflows/debug.md +2 -0
- package/gsd-core/workflows/execute-phase.md +5 -3
- package/gsd-core/workflows/fast.md +8 -22
- package/gsd-core/workflows/progress.md +2 -2
- package/gsd-core/workflows/quick.md +2 -0
- package/gsd-core/workflows/review.md +42 -3
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/ship.md +8 -2
- package/gsd-core/workflows/spec-phase.md +1 -1
- package/gsd-core/workflows/transition.md +1 -1
- package/hooks/dist/gsd-statusline.js +164 -14
- package/hooks/gsd-statusline.js +164 -14
- package/package.json +4 -2
- package/scripts/ci-test-scope.cjs +39 -1
- package/scripts/gen-golden-install-parity-zcode.cjs +35 -45
- package/scripts/gen-install-tree-fixtures.cjs +75 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -1
- package/scripts/lint-table-schema-drift.cjs +157 -0
- package/skills/gsd-mempalace-capture/SKILL.md +27 -1
- package/skills/gsd-surface/SKILL.md +6 -6
- package/vscode/package.json +1 -1
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
{
|
|
10
10
|
"name": "gsd-core",
|
|
11
11
|
"description": "GSD Core is a meta-prompting, context engineering, and spec-driven development system for AI coding agents.",
|
|
12
|
-
"version": "1.7.0
|
|
12
|
+
"version": "1.7.0",
|
|
13
13
|
"source": "./",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "open-gsd",
|
package/agents/gsd-executor.md
CHANGED
|
@@ -315,7 +315,7 @@ For full automation-first patterns, server lifecycle, CLI handling:
|
|
|
315
315
|
**Auto-mode checkpoint behavior** (when `AUTO_CFG` is `"true"`):
|
|
316
316
|
|
|
317
317
|
- **checkpoint:human-verify** → Auto-approve **except package-legitimacy checkpoints**. If checkpoint has `gate="blocking-human"` OR its purpose indicates package legitimacy verification (`what-built` mentions `Package verification required before install` or `Package install failed — human verification required`), do **not** auto-approve. STOP and return checkpoint_return_format for explicit human confirmation.
|
|
318
|
-
- **checkpoint:decision** →
|
|
318
|
+
- **checkpoint:decision** → If checkpoint has `gate="blocking-human"`, do **not** auto-select — STOP and return checkpoint_return_format for an explicit human decision (a `blocking-human` decision exists because its default answer would be wrong to assume). Otherwise auto-select first option (planners front-load the recommended choice), log `⚡ Auto-selected: [option name]`, continue to next task.
|
|
319
319
|
- **checkpoint:human-action** → STOP normally. Auth gates cannot be automated — return structured checkpoint message using checkpoint_return_format.
|
|
320
320
|
|
|
321
321
|
**Standard checkpoint behavior** (when `AUTO_CFG` is not `"true"`):
|
|
@@ -340,6 +340,7 @@ When hitting checkpoint or auth gate, return this structure:
|
|
|
340
340
|
## CHECKPOINT REACHED
|
|
341
341
|
|
|
342
342
|
**Type:** [human-verify | decision | human-action]
|
|
343
|
+
**Gate:** [blocking | blocking-human] — copy the task's `gate` attribute verbatim so the orchestrator's carve-out sees it
|
|
343
344
|
**Plan:** {phase}-{plan}
|
|
344
345
|
**Progress:** {completed}/{total} tasks complete
|
|
345
346
|
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gsd-security-auditor
|
|
3
|
-
description: Verifies threat mitigations from PLAN.md threat model exist in implemented code.
|
|
3
|
+
description: Verifies threat mitigations from PLAN.md threat model exist in implemented code. Returns structured security verdict (SECURED / OPEN_THREATS / ESCALATE). Spawned by /gsd:secure-phase.
|
|
4
4
|
tools:
|
|
5
5
|
- Read
|
|
6
|
-
- Write
|
|
7
|
-
- Edit
|
|
8
6
|
- Bash
|
|
9
7
|
- Glob
|
|
10
8
|
- Grep
|
|
@@ -15,11 +13,11 @@ color: red
|
|
|
15
13
|
<role>
|
|
16
14
|
An implemented phase has been submitted for security audit. Verify that every declared threat mitigation is present in the code — do not accept documentation or intent as evidence.
|
|
17
15
|
|
|
18
|
-
Does NOT scan blindly for new vulnerabilities. Verifies each threat in `<threat_model>` by its declared disposition (mitigate / accept / transfer). Reports gaps.
|
|
16
|
+
Does NOT scan blindly for new vulnerabilities. Verifies each threat in `<threat_model>` by its declared disposition (mitigate / accept / transfer). Reports gaps. Returns a structured verdict — the orchestrator owns the SECURITY.md file write (#2119: single-writer contract).
|
|
19
17
|
|
|
20
18
|
**Mandatory Initial Read:** If prompt contains `<required_reading>`, load ALL listed files before any action.
|
|
21
19
|
|
|
22
|
-
**Implementation files are READ-ONLY.**
|
|
20
|
+
**Implementation files are READ-ONLY.** The auditor does NOT write any files — it returns a structured verdict (SECURED / OPEN_THREATS / ESCALATE). The orchestrator persists SECURITY.md. Implementation security gaps → OPEN_THREATS or ESCALATE. Never patch implementation.
|
|
23
21
|
</role>
|
|
24
22
|
|
|
25
23
|
<adversarial_stance>
|
|
@@ -79,20 +77,20 @@ Classify each threat before verification. Record classification for every threat
|
|
|
79
77
|
- L3: deep trace — follow the data flow end-to-end, check edge cases and ordering, confirm no bypass path exists.
|
|
80
78
|
</step>
|
|
81
79
|
|
|
82
|
-
<step name="
|
|
80
|
+
<step name="verify_and_return">
|
|
83
81
|
For each `mitigate` threat: grep for declared mitigation pattern in cited files → found = `CLOSED`, not found = `OPEN`. Apply depth per `asvs_level` (see analyze_threats step).
|
|
84
|
-
For `accept` threats: check SECURITY.md accepted risks log → entry present = `CLOSED`, absent = `OPEN`.
|
|
82
|
+
For `accept` threats: check existing SECURITY.md accepted risks log → entry present = `CLOSED`, absent = `OPEN`.
|
|
85
83
|
For `transfer` threats: check for transfer documentation → present = `CLOSED`, absent = `OPEN`.
|
|
86
84
|
|
|
87
|
-
For each `threat_flag` in SUMMARY.md `## Threat Flags`: if maps to existing threat ID → informational. If no mapping → log as `unregistered_flag` in
|
|
85
|
+
For each `threat_flag` in SUMMARY.md `## Threat Flags`: if maps to existing threat ID → informational. If no mapping → log as `unregistered_flag` in the structured return (not a blocker).
|
|
88
86
|
|
|
89
87
|
**Severity-aware `threats_open` computation (severity order: critical > high > medium > low):**
|
|
90
88
|
`threats_open` (the SECURITY.md frontmatter gate field) = the count of threats whose status is OPEN AND whose severity rank ≥ the `block_on` rank. `block_on: none` ⇒ 0 (nothing ever blocks). `block_on: low` ⇒ all open threats block. `block_on: high` (default) ⇒ only high and critical open threats block.
|
|
91
|
-
Open threats BELOW the block threshold are recorded in
|
|
89
|
+
Open threats BELOW the block threshold are recorded in the return as **open — below {block_on} threshold (non-blocking)** and MUST NOT be counted in `threats_open`.
|
|
92
90
|
|
|
93
91
|
**Fail-closed for missing severity:** if an OPEN threat has no severity or an unparseable severity (e.g. a legacy register predating the Severity column), treat it as `critical` for this computation — it COUNTS toward `threats_open` (blocking). Never silently drop an unranked open threat.
|
|
94
92
|
|
|
95
|
-
|
|
93
|
+
Return the structured result (SECURED / OPEN_THREATS / ESCALATE) with `threats_open` set to the severity-filtered count. The orchestrator writes SECURITY.md from this data — the auditor does NOT write any files (#2119).
|
|
96
94
|
</step>
|
|
97
95
|
|
|
98
96
|
</execution_flow>
|
|
@@ -116,7 +114,7 @@ Write SECURITY.md. Set `threats_open` to the severity-filtered count. Return str
|
|
|
116
114
|
### Unregistered Flags
|
|
117
115
|
{none / list from SUMMARY.md ## Threat Flags with no threat mapping}
|
|
118
116
|
|
|
119
|
-
|
|
117
|
+
**threats_open:** {count}
|
|
120
118
|
```
|
|
121
119
|
|
|
122
120
|
## OPEN_THREATS
|
|
@@ -145,9 +143,9 @@ SECURITY.md: {path}
|
|
|
145
143
|
|
|
146
144
|
*Only blocking-open threats count toward `threats_open` in SECURITY.md frontmatter.*
|
|
147
145
|
|
|
148
|
-
Next: Implement mitigations or document as accepted
|
|
146
|
+
Next: Implement mitigations or document as accepted risks, then re-run /gsd:secure-phase.
|
|
149
147
|
|
|
150
|
-
|
|
148
|
+
**threats_open:** {count}
|
|
151
149
|
```
|
|
152
150
|
|
|
153
151
|
## ESCALATE
|
|
@@ -172,6 +170,6 @@ SECURITY.md: {path}
|
|
|
172
170
|
- [ ] Each threat verified by disposition type (mitigate / accept / transfer)
|
|
173
171
|
- [ ] Threat flags from SUMMARY.md `## Threat Flags` incorporated
|
|
174
172
|
- [ ] Implementation files never modified
|
|
175
|
-
- [ ]
|
|
176
|
-
- [ ] Structured return: SECURED / OPEN_THREATS / ESCALATE
|
|
173
|
+
- [ ] No files written — structured verdict returned only (orchestrator writes SECURITY.md)
|
|
174
|
+
- [ ] Structured return: SECURED / OPEN_THREATS / ESCALATE with `threats_open` count
|
|
177
175
|
</success_criteria>
|
package/bin/install.js
CHANGED
|
@@ -1139,9 +1139,10 @@ function writeSettings(settingsPath, settings) {
|
|
|
1139
1139
|
* Used by Codex TOML and OpenCode agent file generators to embed per-agent
|
|
1140
1140
|
* model assignments so that model_overrides is respected on non-Claude runtimes (#2256).
|
|
1141
1141
|
*/
|
|
1142
|
-
function readGsdGlobalModelOverrides() {
|
|
1142
|
+
function readGsdGlobalModelOverrides(options = {}) {
|
|
1143
1143
|
try {
|
|
1144
|
-
const
|
|
1144
|
+
const home = options.homedir ? options.homedir() : os.homedir();
|
|
1145
|
+
const defaultsPath = path.join(home, '.gsd', 'defaults.json');
|
|
1145
1146
|
if (!fs.existsSync(defaultsPath)) return null;
|
|
1146
1147
|
const raw = fs.readFileSync(defaultsPath, 'utf-8');
|
|
1147
1148
|
const parsed = JSON.parse(raw);
|
|
@@ -1178,8 +1179,8 @@ function readGsdGlobalModelOverrides() {
|
|
|
1178
1179
|
* Returns a plain `{ agentName: modelId }` object, or `null` when neither
|
|
1179
1180
|
* source defines `model_overrides`.
|
|
1180
1181
|
*/
|
|
1181
|
-
function readGsdEffectiveModelOverrides(targetDir = null) {
|
|
1182
|
-
const global = readGsdGlobalModelOverrides();
|
|
1182
|
+
function readGsdEffectiveModelOverrides(targetDir = null, options = {}) {
|
|
1183
|
+
const global = readGsdGlobalModelOverrides(options);
|
|
1183
1184
|
|
|
1184
1185
|
let projectOverrides = null;
|
|
1185
1186
|
if (targetDir) {
|
|
@@ -55,7 +55,33 @@ This step is `onError: skip` at `discuss:post` / `plan:post` / `verify:post` --
|
|
|
55
55
|
On any error or timeout, stop and let the phase continue -- capture is best-effort.
|
|
56
56
|
|
|
57
57
|
1. **Dedup first.** Interactive: `mempalace_check_duplicate` on the artifact's deterministic drawer id. Headless: rely on `mempalace mine`'s content-hash idempotency.
|
|
58
|
-
2. **Add the drawer (verbatim).** File the exact artifact text into `room: <room>` of `wing: <wing>` with provenance (`source_file`, phase id). Interactive: `mempalace_add_drawer`. Headless:
|
|
58
|
+
2. **Add the drawer (verbatim).** File the exact artifact text into `room: <room>` of `wing: <wing>` with provenance (`source_file`, phase id). Interactive: `mempalace_add_drawer`. Headless: see below.
|
|
59
|
+
|
|
60
|
+
**`mempalace mine` has no `--room` flag** — only `search` accepts `--room` ([CLI reference: https://mempalaceofficial.com/reference/cli.html](https://mempalaceofficial.com/reference/cli.html)). Room assignment is driven by `detect_room()` matching folder-path segments against the `rooms:` list in `mempalace.yaml` ([mining guide: https://mempalaceofficial.com/guide/mining.html](https://mempalaceofficial.com/guide/mining.html) — "Rooms are auto-detected from your folder structure"; [config reference: https://mempalaceofficial.com/guide/configuration.html](https://mempalaceofficial.com/guide/configuration.html)). Stage the artifact under a room-named folder so `detect_room()` assigns it correctly:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
STAGE=".planning/.mempalace-stage"
|
|
64
|
+
# One-time: declare the GSD room taxonomy so detect_room() recognizes these folders
|
|
65
|
+
mkdir -p "$STAGE"
|
|
66
|
+
[ -f "$STAGE/mempalace.yaml" ] || cat > "$STAGE/mempalace.yaml" <<'YAML'
|
|
67
|
+
rooms:
|
|
68
|
+
- decisions
|
|
69
|
+
- planning
|
|
70
|
+
- milestones
|
|
71
|
+
- problems
|
|
72
|
+
- general
|
|
73
|
+
YAML
|
|
74
|
+
# Suppress MemPalace cache artifacts written into the scanned tree
|
|
75
|
+
[ -f "$STAGE/.gitignore" ] || echo "mempalace_embedder.json" > "$STAGE/.gitignore"
|
|
76
|
+
# Stage under <room>/<phase-id>/<basename> — stable path so mine's content-hash
|
|
77
|
+
# idempotency (file_already_mined keys on absolute source_file + mtime) deduplicates
|
|
78
|
+
# instead of creating duplicate drawers on re-runs.
|
|
79
|
+
ROOM_DIR="$STAGE/<room>/<phase-id>"
|
|
80
|
+
mkdir -p "$ROOM_DIR"
|
|
81
|
+
cp "<artifact-path>" "$ROOM_DIR/<basename>"
|
|
82
|
+
# Mine with --wing only — no --room flag; detect_room() assigns from the folder path
|
|
83
|
+
mempalace mine "$STAGE" --wing <wing>
|
|
84
|
+
```
|
|
59
85
|
3. **Mirror KG facts** when `config.mempalace.mirror_kg` is true: extract decision/delivery facts and `mempalace_kg_add` them with `valid_from` = the phase date (e.g. `(<project>, decided, <decision>)` from CONTEXT; `(<phase>, delivered, <capability>)` from SUMMARY). Under `augment` these are an *additive* mirror of GSD's native `.planning/graphs/`. Under `kg_backend`/`replace` the palace KG is the *authoritative* fact store — GSD still produces `.planning/graphs/` through its normal graphify, so an unreachable palace never loses a fact.
|
|
60
86
|
4. Re-running a phase MUST NOT create duplicate drawers (deterministic ids + `check_duplicate`).
|
|
61
87
|
|
package/commands/gsd/surface.md
CHANGED
|
@@ -37,9 +37,9 @@ Parse the first token of $ARGUMENTS:
|
|
|
37
37
|
## list / status
|
|
38
38
|
|
|
39
39
|
Load the capability registry and call `listSurface(runtimeConfigDir, manifest, CLUSTERS, registry)` from
|
|
40
|
-
|
|
40
|
+
the engine module at `${runtimeConfigDir}/gsd-core/bin/lib/surface.cjs`. The registry is loaded via:
|
|
41
41
|
```js
|
|
42
|
-
const registry = require('gsd-core/bin/lib/capability-registry.cjs');
|
|
42
|
+
const registry = require(runtimeConfigDir + '/gsd-core/bin/lib/capability-registry.cjs');
|
|
43
43
|
```
|
|
44
44
|
Display:
|
|
45
45
|
|
|
@@ -71,7 +71,7 @@ Install profile: standard (from .gsd-profile)
|
|
|
71
71
|
3. `writeSurface(runtimeConfigDir, surfaceState)`.
|
|
72
72
|
4. Resolve and re-apply:
|
|
73
73
|
```js
|
|
74
|
-
const registry = require('gsd-core/bin/lib/capability-registry.cjs');
|
|
74
|
+
const registry = require(runtimeConfigDir + '/gsd-core/bin/lib/capability-registry.cjs');
|
|
75
75
|
const layout = resolveRuntimeArtifactLayout(runtime, runtimeConfigDir, scope);
|
|
76
76
|
applySurface(runtimeConfigDir, layout, manifest, CLUSTERS, registry);
|
|
77
77
|
```
|
|
@@ -89,7 +89,7 @@ Valid cluster names: `core_loop`, `audit_review`, `milestone`, `research_ideate`
|
|
|
89
89
|
3. Add cluster to `surfaceState.disabledClusters` (deduplicate).
|
|
90
90
|
4. `writeSurface` → resolve layout → `applySurface`:
|
|
91
91
|
```js
|
|
92
|
-
const registry = require('gsd-core/bin/lib/capability-registry.cjs');
|
|
92
|
+
const registry = require(runtimeConfigDir + '/gsd-core/bin/lib/capability-registry.cjs');
|
|
93
93
|
const layout = resolveRuntimeArtifactLayout(runtime, runtimeConfigDir, scope);
|
|
94
94
|
applySurface(runtimeConfigDir, layout, manifest, CLUSTERS, registry);
|
|
95
95
|
```
|
|
@@ -103,7 +103,7 @@ Valid cluster names: `core_loop`, `audit_review`, `milestone`, `research_ideate`
|
|
|
103
103
|
2. Remove cluster from `surfaceState.disabledClusters`.
|
|
104
104
|
3. `writeSurface` → resolve layout → `applySurface`:
|
|
105
105
|
```js
|
|
106
|
-
const registry = require('gsd-core/bin/lib/capability-registry.cjs');
|
|
106
|
+
const registry = require(runtimeConfigDir + '/gsd-core/bin/lib/capability-registry.cjs');
|
|
107
107
|
const layout = resolveRuntimeArtifactLayout(runtime, runtimeConfigDir, scope);
|
|
108
108
|
applySurface(runtimeConfigDir, layout, manifest, CLUSTERS, registry);
|
|
109
109
|
```
|
|
@@ -151,7 +151,7 @@ All paths can be overridden by reading the `CLAUDE_CONFIG_DIR` env var if set.
|
|
|
151
151
|
|
|
152
152
|
- Unknown cluster name → list valid cluster names, exit without writing.
|
|
153
153
|
- Unknown profile name → list known profiles (`core`, `standard`, `full`), exit.
|
|
154
|
-
- Missing `surface.cjs` → prompt: "Run `npm i -g gsd-core` to reinstall GSD."
|
|
154
|
+
- Missing `surface.cjs` → prompt: "Run `npm i -g @opengsd/gsd-core` to reinstall GSD."
|
|
155
155
|
|
|
156
156
|
<execution_context>
|
|
157
157
|
Surface state file: `~/.claude/.gsd-surface.json`
|
|
@@ -27,6 +27,12 @@
|
|
|
27
27
|
* list-todos [area] Count and enumerate pending todos
|
|
28
28
|
* list-seeds [status] List captured seeds (optional status filter)
|
|
29
29
|
* verify-path-exists <path> Check file/directory existence
|
|
30
|
+
* quick-tasks-append --task <text> Append a row to STATE.md's "Quick Tasks
|
|
31
|
+
* Completed" table (schema-backed via
|
|
32
|
+
* markdown-table.cjs; #2133/ADR-2143).
|
|
33
|
+
* Fails loud (non-zero exit) on a missing
|
|
34
|
+
* or unrecognized table instead of the old
|
|
35
|
+
* awk NF-2 silent-skip guess.
|
|
30
36
|
* config-ensure-section Initialize .planning/config.json
|
|
31
37
|
* history-digest Aggregate all SUMMARY.md data
|
|
32
38
|
* summary-extract <path> [--fields] Extract structured data from SUMMARY.md
|
|
@@ -693,7 +699,7 @@ async function main() {
|
|
|
693
699
|
'from-gsd2, frontmatter, gap-analysis, generate-claude-md, generate-claude-profile, ' +
|
|
694
700
|
'generate-dev-preferences, generate-slug, graphify, history-digest, init, intel, ' +
|
|
695
701
|
'capability, classify-confidence, git, learnings, list-seeds, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, profile-questionnaire, ' +
|
|
696
|
-
'profile-sample, progress, project-instruction-file, prompt-budget, requirements, research-plan, research-store, resolve-granularity, resolve-model, roadmap, scaffold, smart-entry, state, ' +
|
|
702
|
+
'profile-sample, progress, project-instruction-file, prompt-budget, quick-tasks-append, requirements, research-plan, research-store, resolve-granularity, resolve-model, roadmap, scaffold, smart-entry, state, ' +
|
|
697
703
|
'task, template, user-story, validate, verify, verify-path-exists, verify-summary, eval, workstream, worktree\n\n' +
|
|
698
704
|
'Global flags:\n' +
|
|
699
705
|
' --raw Emit raw output without post-processing\n' +
|
|
@@ -1213,6 +1219,59 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1213
1219
|
break;
|
|
1214
1220
|
}
|
|
1215
1221
|
|
|
1222
|
+
case 'quick-tasks-append': {
|
|
1223
|
+
// #2133 / ADR-2143 §3,§7: schema-backed replacement for fast.md's inline
|
|
1224
|
+
// `awk NF-2` Quick Tasks column arithmetic. Row construction is delegated
|
|
1225
|
+
// to the pure appendQuickTaskRow (markdown-table.cjs); this case only
|
|
1226
|
+
// handles the I/O (read STATE.md, resolve date/commit, write STATE.md).
|
|
1227
|
+
const qtaArgs = args.slice(1);
|
|
1228
|
+
const qtaTask = parseNamedArgs(qtaArgs, ['task']).task || args[1];
|
|
1229
|
+
if (!qtaTask) {
|
|
1230
|
+
error('quick-tasks-append requires --task <description> (or a positional description)', ERROR_REASON.USAGE);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
const statePath = path.join(cwd, '.planning', 'STATE.md');
|
|
1234
|
+
if (!fs.existsSync(statePath)) {
|
|
1235
|
+
error(`quick-tasks-append: STATE.md not found at ${statePath}`, ERROR_REASON.USAGE);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
1239
|
+
const { execGit } = require('./lib/shell-command-projection.cjs');
|
|
1240
|
+
const hashResult = execGit(['rev-parse', '--short', 'HEAD'], { cwd });
|
|
1241
|
+
const commit = hashResult.exitCode === 0 && hashResult.stdout ? hashResult.stdout : '—';
|
|
1242
|
+
|
|
1243
|
+
const { appendQuickTaskRow } = require('./lib/markdown-table.cjs');
|
|
1244
|
+
|
|
1245
|
+
// #2242 review fix: route the read -> mutate -> write cycle through
|
|
1246
|
+
// state.readModifyWriteStateMd (lib/state.cjs) instead of a raw
|
|
1247
|
+
// fs.readFileSync + fs.writeFileSync pair, so the whole read-modify-write
|
|
1248
|
+
// is atomic under STATE.md's lockfile — closing the lost-update race a
|
|
1249
|
+
// raw read/write pair left open (cf. #500/#905/#1230). This mirrors the
|
|
1250
|
+
// pattern every other STATE.md-mutating case in state.cts uses (e.g.
|
|
1251
|
+
// cmdStateAddBlocker, cmdStateAddDecision): a mutable outer variable
|
|
1252
|
+
// captures the pure helper's side output, and a fail-loud reason throws
|
|
1253
|
+
// ExitError from INSIDE the transform (readModifyWriteStateMd's finally
|
|
1254
|
+
// still releases the lock before the throw propagates; the transform
|
|
1255
|
+
// throws before returning new content, so nothing is ever written).
|
|
1256
|
+
let mutation;
|
|
1257
|
+
state.readModifyWriteStateMd(statePath, (content) => {
|
|
1258
|
+
const result = appendQuickTaskRow(content, { description: qtaTask, date, commit });
|
|
1259
|
+
if (!result.ok) {
|
|
1260
|
+
// Mirrors fast.md's old "skip with a brief log" behaviour (#2133): this
|
|
1261
|
+
// is an expected, recoverable condition (no table / unrecognized
|
|
1262
|
+
// schema), not a hard crash. ExitError sets a non-zero exit code (so
|
|
1263
|
+
// fast.md's `|| echo ...` fallback fires) without calling
|
|
1264
|
+
// process.exit() directly — stdout stays flushed and untouched.
|
|
1265
|
+
throw new ExitError(1, `⚠ quick-tasks-append: ${result.reason}`);
|
|
1266
|
+
}
|
|
1267
|
+
mutation = result.value;
|
|
1268
|
+
return result.value.content;
|
|
1269
|
+
}, cwd);
|
|
1270
|
+
|
|
1271
|
+
output({ ok: true, row: mutation.row, variant: mutation.variant }, raw, mutation.row);
|
|
1272
|
+
break;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1216
1275
|
case 'config-ensure-section': {
|
|
1217
1276
|
// Phase 6 (#3575): dispatch via SDK executeForCjs. The catalog rebinds
|
|
1218
1277
|
// 'config-ensure-section' to configNewProject in
|
|
@@ -1500,7 +1559,9 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1500
1559
|
// new-milestone never inherits un-archived dirs. --no-archive-phases opts out.
|
|
1501
1560
|
const archivePhases = !args.includes('--no-archive-phases');
|
|
1502
1561
|
const force = args.includes('--force');
|
|
1503
|
-
|
|
1562
|
+
// #2118: --dry-run prints a preview plan without mutating.
|
|
1563
|
+
const dryRun = args.includes('--dry-run');
|
|
1564
|
+
milestone.cmdMilestoneComplete(cwd, args[2], { name: milestoneName, archivePhases, force, dryRun }, raw);
|
|
1504
1565
|
} else {
|
|
1505
1566
|
error('Unknown milestone subcommand. Available: complete', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
1506
1567
|
}
|
|
@@ -264,13 +264,12 @@ function parseCoverageMatrix(text) {
|
|
|
264
264
|
const src = text.replace(/\r\n/g, '\n');
|
|
265
265
|
// (1) fenced ```coverage JSON block takes precedence if present.
|
|
266
266
|
// Case-insensitive info string (```coverage and ```Coverage are both legal CommonMark).
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if (fenceMatch && fenceMatch[1]) {
|
|
267
|
+
const fenceBody = (0, markdown_sectionizer_cjs_1.extractFencedBlock)(src, 'coverage');
|
|
268
|
+
if (fenceBody) {
|
|
270
269
|
out.format = 'json';
|
|
271
270
|
let parsed;
|
|
272
271
|
try {
|
|
273
|
-
parsed = JSON.parse(
|
|
272
|
+
parsed = JSON.parse(fenceBody);
|
|
274
273
|
}
|
|
275
274
|
catch {
|
|
276
275
|
out.errors.push('fenced ```coverage block is not valid JSON');
|
|
@@ -17,6 +17,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
17
17
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
18
18
|
const node_path_1 = __importDefault(require("node:path"));
|
|
19
19
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
20
|
+
const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
20
21
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
21
22
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
22
23
|
const { planningDir } = planningWorkspace;
|
|
@@ -71,9 +72,9 @@ function scanDebugSessions(planDir) {
|
|
|
71
72
|
continue;
|
|
72
73
|
// Extract hypothesis from "Current Focus" block if parseable
|
|
73
74
|
let hypothesis = '';
|
|
74
|
-
const
|
|
75
|
-
if (
|
|
76
|
-
const focusText =
|
|
75
|
+
const focusSection = (0, markdown_sectionizer_cjs_1.collectSection)(content, (h) => h.level === 2 && h.text.trim().toLowerCase().startsWith('current focus'), { levelBounded: true });
|
|
76
|
+
if (focusSection) {
|
|
77
|
+
const focusText = focusSection.body.trim().split('\n')[0].trim();
|
|
77
78
|
hypothesis = (0, security_cjs_1.sanitizeForDisplay)(focusText.slice(0, 100));
|
|
78
79
|
}
|
|
79
80
|
const slug = node_path_1.default.basename(entry.name, '.md');
|
|
@@ -509,9 +510,9 @@ function scanContextQuestions(planDir) {
|
|
|
509
510
|
}
|
|
510
511
|
// Also check for ## Open Questions section in body
|
|
511
512
|
if (questions.length === 0) {
|
|
512
|
-
const
|
|
513
|
-
if (
|
|
514
|
-
const oqBody =
|
|
513
|
+
const oqSection = (0, markdown_sectionizer_cjs_1.collectSection)(content, (h) => h.level === 2 && h.text.trim().toLowerCase().startsWith('open questions'), { levelBounded: true });
|
|
514
|
+
if (oqSection) {
|
|
515
|
+
const oqBody = oqSection.body.trim();
|
|
515
516
|
if (oqBody && oqBody.length > 0 && !/^\s*none\s*$/i.test(oqBody)) {
|
|
516
517
|
const items = oqBody.split('\n')
|
|
517
518
|
.map((l) => l.trim())
|