@opengsd/gsd-core 1.7.0-rc.2 → 1.7.0-rc.3
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/GEMINI.md +25 -23
- package/README.md +3 -3
- package/agents/gsd-advisor-researcher.md +1 -1
- package/agents/gsd-ai-researcher.md +1 -1
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-debug-session-manager.md +1 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-domain-researcher.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +4 -4
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +1 -1
- package/agents/gsd-phase-researcher.md +2 -2
- package/agents/gsd-plan-checker.md +1 -1
- package/agents/gsd-planner.md +2 -2
- package/agents/gsd-project-researcher.md +2 -2
- package/agents/gsd-research-synthesizer.md +1 -1
- package/agents/gsd-ui-researcher.md +2 -2
- package/agents/gsd-verifier.md +2 -2
- package/bin/install.js +108 -458
- package/commands/gsd/mempalace-capture.md +1 -1
- package/commands/gsd/mempalace-recall.md +9 -5
- package/commands/gsd/next.md +29 -0
- package/commands/gsd/ns-workflow.md +5 -2
- package/gsd-core/bin/ensure-runtime-build.cjs +246 -0
- package/gsd-core/bin/gsd-tools.cjs +25 -1
- package/gsd-core/bin/lib/capability-registry.cjs +207 -205
- package/gsd-core/bin/lib/check-command-router.cjs +111 -1
- package/gsd-core/bin/lib/clusters.cjs +1 -0
- package/gsd-core/bin/lib/config.cjs +19 -1
- package/gsd-core/bin/lib/core-utils.cjs +41 -0
- package/gsd-core/bin/lib/external-job.cjs +287 -0
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +151 -0
- package/gsd-core/bin/lib/graphify.cjs +41 -9
- package/gsd-core/bin/lib/milestone.cjs +7 -1
- package/gsd-core/bin/lib/phase.cjs +14 -2
- package/gsd-core/bin/lib/plan-scan.cjs +9 -1
- package/gsd-core/bin/lib/profile-output.cjs +1 -1
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +21 -19
- package/gsd-core/bin/lib/roadmap.cjs +17 -2
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +18 -13
- package/gsd-core/bin/lib/runtime-name-policy.cjs +8 -10
- package/gsd-core/bin/lib/shell-command-projection.cjs +12 -8
- package/gsd-core/bin/lib/smart-entry.cjs +521 -0
- package/gsd-core/bin/lib/surface.cjs +18 -9
- package/gsd-core/bin/lib/update-context.cjs +0 -5
- package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
- package/gsd-core/bin/shared/model-catalog.json +0 -5
- package/gsd-core/bin/shared/runtime-aliases.manifest.json +0 -5
- package/gsd-core/references/gates.md +1 -1
- package/gsd-core/references/planning-config.md +1 -1
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +1 -1
- package/gsd-core/workflows/add-tests.md +1 -1
- package/gsd-core/workflows/add-todo.md +1 -1
- package/gsd-core/workflows/ai-integration-phase.md +1 -1
- package/gsd-core/workflows/audit-fix.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +1 -1
- package/gsd-core/workflows/audit-uat.md +1 -1
- package/gsd-core/workflows/autonomous.md +1 -1
- package/gsd-core/workflows/check-todos.md +1 -1
- package/gsd-core/workflows/cleanup.md +1 -1
- package/gsd-core/workflows/code-review-fix.md +1 -1
- package/gsd-core/workflows/code-review.md +1 -1
- package/gsd-core/workflows/complete-milestone.md +1 -1
- package/gsd-core/workflows/debug.md +1 -1
- package/gsd-core/workflows/diagnose-issues.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/auto.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase.md +2 -2
- package/gsd-core/workflows/do.md +1 -1
- package/gsd-core/workflows/docs-update.md +1 -1
- package/gsd-core/workflows/edit-phase.md +1 -1
- package/gsd-core/workflows/eval-review.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +1 -1
- package/gsd-core/workflows/execute-phase.md +4 -5
- package/gsd-core/workflows/execute-plan.md +1 -1
- package/gsd-core/workflows/explore.md +1 -1
- package/gsd-core/workflows/extract-learnings.md +1 -1
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +1 -1
- package/gsd-core/workflows/help/modes/full.md +14 -0
- package/gsd-core/workflows/help/modes/topic.md +1 -0
- package/gsd-core/workflows/import.md +1 -1
- package/gsd-core/workflows/ingest-docs.md +1 -1
- package/gsd-core/workflows/insert-phase.md +1 -1
- package/gsd-core/workflows/list-seeds.md +1 -1
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +1 -1
- package/gsd-core/workflows/map-codebase.md +1 -1
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +1 -1
- package/gsd-core/workflows/new-project.md +1 -1
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +1 -1
- package/gsd-core/workflows/pause-work.md +1 -1
- package/gsd-core/workflows/plan-milestone-gaps.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase.md +9 -2
- package/gsd-core/workflows/plan-review-convergence.md +1 -1
- package/gsd-core/workflows/plant-seed.md +1 -1
- package/gsd-core/workflows/pr-branch.md +1 -1
- package/gsd-core/workflows/profile-user.md +1 -1
- package/gsd-core/workflows/progress.md +1 -1
- package/gsd-core/workflows/quick.md +1 -1
- package/gsd-core/workflows/reapply-patches.md +1 -1
- package/gsd-core/workflows/remove-phase.md +1 -1
- package/gsd-core/workflows/remove-workspace.md +1 -1
- package/gsd-core/workflows/resume-project.md +1 -1
- package/gsd-core/workflows/review.md +43 -7
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/settings-advanced.md +3 -1
- package/gsd-core/workflows/settings-integrations.md +1 -1
- package/gsd-core/workflows/settings.md +1 -1
- package/gsd-core/workflows/ship.md +1 -1
- package/gsd-core/workflows/sketch-wrap-up.md +1 -1
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +120 -0
- package/gsd-core/workflows/spec-phase.md +8 -6
- package/gsd-core/workflows/spike-wrap-up.md +1 -1
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +1 -1
- package/gsd-core/workflows/thread.md +1 -1
- package/gsd-core/workflows/transition.md +1 -1
- package/gsd-core/workflows/ui-phase.md +1 -1
- package/gsd-core/workflows/ui-review.md +1 -1
- package/gsd-core/workflows/ultraplan-phase.md +1 -1
- package/gsd-core/workflows/update.md +3 -6
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-phase.md +1 -1
- package/gsd-core/workflows/verify-work.md +62 -4
- package/package.json +3 -5
- package/scripts/ci-test-scope.cjs +19 -1
- package/scripts/generate-package-identity.cjs +27 -2
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -148
- package/scripts/lint-regression-test-names.allowlist.json +1 -273
- package/scripts/lint-test-file-count.allowlist.json +7 -91
- package/scripts/research-profiles.cjs +6 -6
- package/scripts/run-tests.cjs +16 -3
- package/scripts/slurm-adapter.cjs +269 -0
- package/scripts/sync-manifest-versions.cjs +3 -2
- package/skills/gsd-mempalace-capture/SKILL.md +1 -1
- package/skills/gsd-mempalace-recall/SKILL.md +9 -5
- package/skills/gsd-next/SKILL.md +30 -0
- package/skills/gsd-ns-workflow/SKILL.md +4 -1
- package/gemini-extension.json +0 -6
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Smart Entry — state-aware situation classifier for the /gsd front door.
|
|
4
|
+
*
|
|
5
|
+
* Reads project + workflow state (`.planning/STATE.md`, ROADMAP.md, git) and
|
|
6
|
+
* classifies the user's situation into one of a small set of enumerated values,
|
|
7
|
+
* each carrying a recommended next action and a menu of alternatives. Pure
|
|
8
|
+
* detection → classification; no side effects, no writes.
|
|
9
|
+
*
|
|
10
|
+
* Surfaced as `gsd-tools smart-entry [--json]`. The markdown `/gsd` command +
|
|
11
|
+
* workflow shells out to `smart-entry --json`, renders an AskUserQuestion menu
|
|
12
|
+
* (with a --text numbered-list fallback for non-Claude runtimes), and dispatches
|
|
13
|
+
* the chosen action to an existing slash command. See
|
|
14
|
+
* docs/superpowers/specs/2026-06-27-gsd-smart-entry-design.md and
|
|
15
|
+
* docs/adr/1787-gsd-next-smart-entry.md.
|
|
16
|
+
*
|
|
17
|
+
* Relationship to `/gsd:progress --next`: this classifier is a *menu* front door,
|
|
18
|
+
* not a second router. For in-project forward motion (planning → executing →
|
|
19
|
+
* verify-pending) the recommended action delegates to `/gsd:progress --next`
|
|
20
|
+
* (workflows/next.md) — the single, gated advancement engine (Route 0 resume-
|
|
21
|
+
* incomplete-phase invariant + Gates 1-3). smart-entry never re-derives forward
|
|
22
|
+
* routing itself; a standalone advancement command that bypassed those gates is
|
|
23
|
+
* exactly what got the old flat `/gsd-next` removed (#3054). Its distinct value is
|
|
24
|
+
* the states `--next` cannot reach: pre-project (no-project), remediation (paused,
|
|
25
|
+
* blocked, verify-failed), and lifecycle exits (idle-stranded, complete).
|
|
26
|
+
*
|
|
27
|
+
* Design note: this is the gsd-core analog of gsd-pi's `showSmartEntry` branch
|
|
28
|
+
* tree, redesigned for gsd-core's `.planning/` phase loop. gsd-pi's
|
|
29
|
+
* milestone/slice/task model does not exist here; the situation table is the
|
|
30
|
+
* translation, not a port.
|
|
31
|
+
*/
|
|
32
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
33
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
34
|
+
};
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.SITUATIONS = void 0;
|
|
37
|
+
exports.detectSignals = detectSignals;
|
|
38
|
+
exports.classify = classify;
|
|
39
|
+
exports.actionsFor = actionsFor;
|
|
40
|
+
exports.classifyProject = classifyProject;
|
|
41
|
+
exports.runSmartEntry = runSmartEntry;
|
|
42
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
43
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
44
|
+
const node_child_process_1 = require("node:child_process");
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
46
|
+
const ioMod = require("./io.cjs");
|
|
47
|
+
const { output } = ioMod;
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
49
|
+
const planningWorkspace = require("./planning-workspace.cjs");
|
|
50
|
+
const { planningPaths } = planningWorkspace;
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
52
|
+
const frontmatter = require("./frontmatter.cjs");
|
|
53
|
+
const { extractFrontmatter } = frontmatter;
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
55
|
+
const stateDocument = require("./state-document.cjs");
|
|
56
|
+
const { stateExtractField } = stateDocument;
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
58
|
+
const phaseId = require("./phase-id.cjs");
|
|
59
|
+
const { comparePhaseNum, extractPhaseToken, normalizePhaseName, phaseTokenMatches } = phaseId;
|
|
60
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
61
|
+
/**
|
|
62
|
+
* Staleness threshold for idle-stranded detection. A clean tree whose last
|
|
63
|
+
* activity is older than this (with non-complete status) is considered stranded
|
|
64
|
+
* committed-but-unshipped work. Hardcoded for v1; configurable later.
|
|
65
|
+
*/
|
|
66
|
+
const IDLE_STALE_MS = 72 * 60 * 60 * 1000; // 72h
|
|
67
|
+
/** @internal a single source of truth so the structure test can assert coverage. */
|
|
68
|
+
exports.SITUATIONS = Object.freeze([
|
|
69
|
+
'no-project',
|
|
70
|
+
'paused',
|
|
71
|
+
'blocked',
|
|
72
|
+
'verify-failed',
|
|
73
|
+
'needs-first-phase',
|
|
74
|
+
'planning',
|
|
75
|
+
'executing',
|
|
76
|
+
'verify-pending',
|
|
77
|
+
'idle-stranded',
|
|
78
|
+
'complete',
|
|
79
|
+
'unknown',
|
|
80
|
+
]);
|
|
81
|
+
// ─── Detection ─────────────────────────────────────────────────────────────────
|
|
82
|
+
/** Frontmatter scalar helper: prefer YAML frontmatter, fall back to body field. */
|
|
83
|
+
function fmScalar(fm, body, key, bodyField) {
|
|
84
|
+
const v = fm[key];
|
|
85
|
+
if (typeof v === 'string' && v.trim())
|
|
86
|
+
return v.trim();
|
|
87
|
+
if (typeof v === 'number' || typeof v === 'boolean')
|
|
88
|
+
return String(v);
|
|
89
|
+
return stateExtractField(body, bodyField);
|
|
90
|
+
}
|
|
91
|
+
/** Read a scalar value from a nested frontmatter object (e.g. progress.total_phases). */
|
|
92
|
+
function fmScalarKey(obj, key) {
|
|
93
|
+
if (!obj || typeof obj !== 'object')
|
|
94
|
+
return null;
|
|
95
|
+
const v = obj[key];
|
|
96
|
+
if (typeof v === 'string' && v.trim())
|
|
97
|
+
return v.trim();
|
|
98
|
+
if (typeof v === 'number' || typeof v === 'boolean')
|
|
99
|
+
return String(v);
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
function parseIntOrNull(s) {
|
|
103
|
+
if (s === null)
|
|
104
|
+
return null;
|
|
105
|
+
const cleaned = s.replace('%', '').trim();
|
|
106
|
+
const n = Number.parseInt(cleaned, 10);
|
|
107
|
+
return Number.isFinite(n) ? n : null;
|
|
108
|
+
}
|
|
109
|
+
function phaseTokenFromDirName(name) {
|
|
110
|
+
const token = extractPhaseToken(name);
|
|
111
|
+
return /^\d+(?:[A-Z])?(?:\.\d+)*(?:-|$)/i.test(token) ? token : null;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Parse a `last_activity` value that may be an ISO date or a free-form string
|
|
115
|
+
* into an epoch-ms timestamp. Returns null when unparseable.
|
|
116
|
+
*/
|
|
117
|
+
function parseActivityTimestamp(raw) {
|
|
118
|
+
if (!raw)
|
|
119
|
+
return null;
|
|
120
|
+
const ms = Date.parse(raw);
|
|
121
|
+
return Number.isNaN(ms) ? null : ms;
|
|
122
|
+
}
|
|
123
|
+
/** Read-only git signals. Any git error is swallowed → "no git signal". */
|
|
124
|
+
function readGitSignals(cwd) {
|
|
125
|
+
const run = (args) => {
|
|
126
|
+
try {
|
|
127
|
+
return (0, node_child_process_1.execFileSync)('git', args, {
|
|
128
|
+
cwd,
|
|
129
|
+
encoding: 'utf-8',
|
|
130
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
131
|
+
windowsHide: true,
|
|
132
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return '';
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
// Cheap "is this a git repo" probe that also doubles as the dirty check.
|
|
140
|
+
const status = run(['status', '--porcelain']);
|
|
141
|
+
if (status === '' && !run(['rev-parse', '--is-inside-work-tree']).trim()) {
|
|
142
|
+
return { has_git: false, dirty: false, unpushed: false };
|
|
143
|
+
}
|
|
144
|
+
const dirty = status.trim().length > 0;
|
|
145
|
+
// `git log @{u}..HEAD` lists commits on this branch not on its upstream.
|
|
146
|
+
// Non-empty ⇒ unpushed. Errors (no upstream, detached HEAD) ⇒ not unpushed.
|
|
147
|
+
const unpushed = run(['log', '@{u}..HEAD', '--oneline']).trim().length > 0;
|
|
148
|
+
return { has_git: true, dirty, unpushed };
|
|
149
|
+
}
|
|
150
|
+
/** Leading numeric phase token from a STATE.md scalar or body `Phase:` value. */
|
|
151
|
+
function phaseTokenFromState(raw) {
|
|
152
|
+
if (!raw?.trim())
|
|
153
|
+
return null;
|
|
154
|
+
const match = raw.trim().match(/^(\d+(?:[A-Z])?(?:\.\d+)*)/i);
|
|
155
|
+
return match ? match[1] : null;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Detect whether the current phase's verify report indicates failure. The
|
|
159
|
+
* canonical signal is a phase summary/verify artifact whose STATUS: marker reads
|
|
160
|
+
* blocked, failed, or fail. Scoped to STATE.md's current phase so a leftover or
|
|
161
|
+
* newer phase tree cannot skew routing. Returns true only on a clear positive signal.
|
|
162
|
+
*/
|
|
163
|
+
function detectVerifyFailed(cwd, currentPhaseRaw) {
|
|
164
|
+
const phasesDir = node_path_1.default.join(planningPaths(cwd).phases);
|
|
165
|
+
let entries = [];
|
|
166
|
+
try {
|
|
167
|
+
entries = node_fs_1.default.readdirSync(phasesDir)
|
|
168
|
+
.map((name) => ({ name, phaseToken: phaseTokenFromDirName(name) }))
|
|
169
|
+
.filter((entry) => entry.phaseToken !== null)
|
|
170
|
+
.sort((a, b) => comparePhaseNum(a.phaseToken, b.phaseToken) || a.name.localeCompare(b.name))
|
|
171
|
+
.map((entry) => entry.name);
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
if (entries.length === 0)
|
|
177
|
+
return false;
|
|
178
|
+
const phaseToken = phaseTokenFromState(currentPhaseRaw);
|
|
179
|
+
let targetDir;
|
|
180
|
+
if (phaseToken) {
|
|
181
|
+
const normalized = normalizePhaseName(phaseToken);
|
|
182
|
+
targetDir = entries.find((name) => phaseTokenMatches(name, normalized));
|
|
183
|
+
if (!targetDir)
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
// No current phase in state — fall back to the highest-numbered phase dir.
|
|
188
|
+
targetDir = entries[entries.length - 1];
|
|
189
|
+
}
|
|
190
|
+
const latestDir = node_path_1.default.join(phasesDir, targetDir);
|
|
191
|
+
let files = [];
|
|
192
|
+
try {
|
|
193
|
+
files = node_fs_1.default.readdirSync(latestDir);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
const candidates = files.filter((f) => /summary|verif(?:y|ication)|uat/i.test(f));
|
|
199
|
+
for (const name of candidates) {
|
|
200
|
+
let content = '';
|
|
201
|
+
try {
|
|
202
|
+
content = node_fs_1.default.readFileSync(node_path_1.default.join(latestDir, name), 'utf-8');
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const statusMatch = content.match(/STATUS:\s*([A-Za-z_-]+)/i);
|
|
208
|
+
if (statusMatch && /\b(blocked|fail(ed)?)\b/i.test(statusMatch[1])) {
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
/** Parse `.planning/STATE.md` (frontmatter + body) into the raw signals we need. */
|
|
215
|
+
function readStateFile(statePath) {
|
|
216
|
+
let content;
|
|
217
|
+
try {
|
|
218
|
+
content = node_fs_1.default.readFileSync(statePath, 'utf-8');
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
const fm = extractFrontmatter(content);
|
|
224
|
+
const body = content.replace(/^---[\s\S]*?---\s*/, '');
|
|
225
|
+
return { fm, body };
|
|
226
|
+
}
|
|
227
|
+
/** Collect the full signal set from disk for `cwd`. Exported for unit tests. */
|
|
228
|
+
function detectSignals(cwd, now = Date.now) {
|
|
229
|
+
const paths = planningPaths(cwd);
|
|
230
|
+
const hasPlanning = node_fs_1.default.existsSync(paths.planning);
|
|
231
|
+
const hasRoadmap = node_fs_1.default.existsSync(paths.roadmap);
|
|
232
|
+
const git = readGitSignals(cwd);
|
|
233
|
+
const empty = {
|
|
234
|
+
current_phase: null,
|
|
235
|
+
total_phases: null,
|
|
236
|
+
status: '',
|
|
237
|
+
progress: null,
|
|
238
|
+
has_planning: hasPlanning,
|
|
239
|
+
has_roadmap: hasRoadmap,
|
|
240
|
+
git_dirty: git.dirty,
|
|
241
|
+
git_unpushed: git.unpushed,
|
|
242
|
+
paused: false,
|
|
243
|
+
blockers: [],
|
|
244
|
+
has_git: git.has_git,
|
|
245
|
+
verify_failed: false,
|
|
246
|
+
stale_activity: false,
|
|
247
|
+
};
|
|
248
|
+
if (!hasPlanning)
|
|
249
|
+
return empty;
|
|
250
|
+
const parsed = readStateFile(paths.state);
|
|
251
|
+
if (!parsed)
|
|
252
|
+
return empty;
|
|
253
|
+
const { fm, body } = parsed;
|
|
254
|
+
// STATE.md has two lineages of schemas (see state.cjs parseProsePhaseField):
|
|
255
|
+
// - scalar frontmatter: `current_phase: 3`, `total_phases: 5`
|
|
256
|
+
// - nested frontmatter: `progress: { total_phases: 5, percent: 40 }`
|
|
257
|
+
// - body prose: `Phase: 3`, `**Status:** verifying`, `Total Phases: 5`
|
|
258
|
+
// Read each field across every form, scalar-first then nested then body, so
|
|
259
|
+
// the classifier works on real STATE.md files written by current GSD.
|
|
260
|
+
const statusRaw = fmScalar(fm, body, 'status', 'Status');
|
|
261
|
+
const pausedAtRaw = fmScalar(fm, body, 'paused_at', 'Paused At');
|
|
262
|
+
const lastActivityRaw = fmScalar(fm, body, 'last_activity', 'Last Activity');
|
|
263
|
+
// current_phase: scalar fm → nested (none) → body "Current Phase" → body "Phase".
|
|
264
|
+
// The body `Phase:` field is the canonical location in prose-form STATE.md
|
|
265
|
+
// (e.g. "Phase: 3" or "Phase: 3 — ui-review"); parse the leading number.
|
|
266
|
+
const currentPhaseRaw = fmScalar(fm, body, 'current_phase', 'Current Phase') ??
|
|
267
|
+
stateExtractField(body, 'Phase');
|
|
268
|
+
// total_phases & percent: nested `progress:` object takes precedence in the
|
|
269
|
+
// nested schema; scalar fm / body fields cover the flat schema.
|
|
270
|
+
const progressFm = typeof fm.progress === 'object' ? fm.progress : null;
|
|
271
|
+
const totalPhasesRaw = fmScalarKey(progressFm, 'total_phases') ??
|
|
272
|
+
fmScalar(fm, body, 'total_phases', 'Total Phases');
|
|
273
|
+
const progressRaw = fmScalarKey(progressFm, 'percent') ??
|
|
274
|
+
fmScalar(fm, body, 'progress', 'Progress');
|
|
275
|
+
// Blockers list: `- <text>` items under a `## Blockers` heading.
|
|
276
|
+
const blockers = [];
|
|
277
|
+
const blockersMatch = body.match(/##\s*Blockers\s*\n([\s\S]*?)(?=\n##|$)/i); // allow-adhoc-markdown: read-only blockers section-collect in smart-entry.cts; mirrors state.cts (#1372), pending collectSection migration
|
|
278
|
+
if (blockersMatch) {
|
|
279
|
+
const items = blockersMatch[1].match(/^-\s+(.+)$/gm) || [];
|
|
280
|
+
for (const item of items)
|
|
281
|
+
blockers.push(item.replace(/^-\s+/, '').trim());
|
|
282
|
+
}
|
|
283
|
+
const paused = Boolean(pausedAtRaw && pausedAtRaw.trim());
|
|
284
|
+
// Stale = no recorded activity for IDLE_STALE_MS. Used only by idle-stranded.
|
|
285
|
+
// Computed here (with the clock seam) so the pure classify() stays a function
|
|
286
|
+
// of (signals, staleActivity) and detectSignals owns all disk reads.
|
|
287
|
+
const lastActivityMs = parseActivityTimestamp(lastActivityRaw);
|
|
288
|
+
const staleActivity = lastActivityMs !== null && now() - lastActivityMs > IDLE_STALE_MS;
|
|
289
|
+
// Verify-failed may be signalled either by STATE.md status or by a failed
|
|
290
|
+
// STATUS: marker on the current phase's summary/verify artifact.
|
|
291
|
+
const verifyFailed = /\bverify-fail(ed)?|verification-fail|uat-fail\b/i.test(statusRaw || '') ||
|
|
292
|
+
detectVerifyFailed(cwd, currentPhaseRaw);
|
|
293
|
+
return {
|
|
294
|
+
current_phase: parseIntOrNull(currentPhaseRaw),
|
|
295
|
+
total_phases: parseIntOrNull(totalPhasesRaw),
|
|
296
|
+
status: (statusRaw || '').toLowerCase(),
|
|
297
|
+
progress: parseIntOrNull(progressRaw),
|
|
298
|
+
has_planning: hasPlanning,
|
|
299
|
+
has_roadmap: hasRoadmap,
|
|
300
|
+
git_dirty: git.dirty,
|
|
301
|
+
git_unpushed: git.unpushed,
|
|
302
|
+
paused,
|
|
303
|
+
blockers,
|
|
304
|
+
has_git: git.has_git,
|
|
305
|
+
verify_failed: verifyFailed,
|
|
306
|
+
stale_activity: staleActivity,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
// ─── Situation classification ─────────────────────────────────────────────────
|
|
310
|
+
/** True when the workflow has fully completed all phases. */
|
|
311
|
+
function isComplete(s) {
|
|
312
|
+
if (s.total_phases === null || s.current_phase === null)
|
|
313
|
+
return false;
|
|
314
|
+
return s.current_phase >= s.total_phases && /\bcomplete(d)?|done|shipped\b/i.test(s.status);
|
|
315
|
+
}
|
|
316
|
+
/** Idle-stranded: clean tree, committed work not shipped, optionally stale. */
|
|
317
|
+
function isIdleStranded(s) {
|
|
318
|
+
if (/\bcomplete(d)?|done|shipped|paused\b/i.test(s.status))
|
|
319
|
+
return false;
|
|
320
|
+
if (s.git_dirty)
|
|
321
|
+
return false;
|
|
322
|
+
// Unpushed commits are the strongest signal. Otherwise require staleness.
|
|
323
|
+
return s.git_unpushed || s.stale_activity;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Classify signals into a situation. Priority order matters — the first
|
|
327
|
+
* matching predicate wins (e.g. paused beats blocked). Exported for unit tests.
|
|
328
|
+
* Pure function of signals (staleness + verify-failed are precomputed on signals).
|
|
329
|
+
*/
|
|
330
|
+
function classify(s) {
|
|
331
|
+
if (!s.has_planning)
|
|
332
|
+
return 'no-project';
|
|
333
|
+
if (s.paused)
|
|
334
|
+
return 'paused';
|
|
335
|
+
if (s.blockers.length > 0)
|
|
336
|
+
return 'blocked';
|
|
337
|
+
if (s.verify_failed)
|
|
338
|
+
return 'verify-failed';
|
|
339
|
+
if (s.total_phases === null || s.total_phases <= 0 || !s.has_roadmap)
|
|
340
|
+
return 'needs-first-phase';
|
|
341
|
+
if (isComplete(s))
|
|
342
|
+
return 'complete';
|
|
343
|
+
if (/\bplanning|planned\b/i.test(s.status))
|
|
344
|
+
return 'planning';
|
|
345
|
+
if (/\bexecut(e|ing)|active|in.progress|building\b/i.test(s.status))
|
|
346
|
+
return 'executing';
|
|
347
|
+
if (/\bverify|review|needs.review|pending.review\b/i.test(s.status))
|
|
348
|
+
return 'verify-pending';
|
|
349
|
+
if (isIdleStranded(s))
|
|
350
|
+
return 'idle-stranded';
|
|
351
|
+
return 'unknown';
|
|
352
|
+
}
|
|
353
|
+
// ─── Action sets ──────────────────────────────────────────────────────────────
|
|
354
|
+
const action = (id, label, command, recommended = false) => ({ id, label, command, recommended });
|
|
355
|
+
function rec(id, label, command) {
|
|
356
|
+
return action(id, label, command, true);
|
|
357
|
+
}
|
|
358
|
+
/** Per-situation ordered action list; recommended action first. */
|
|
359
|
+
function actionsFor(situation, s) {
|
|
360
|
+
const phaseN = s.current_phase ?? '';
|
|
361
|
+
const execLabel = phaseN === '' ? 'Continue executing' : `Continue executing phase ${phaseN}`;
|
|
362
|
+
switch (situation) {
|
|
363
|
+
case 'no-project':
|
|
364
|
+
return [
|
|
365
|
+
rec('new-project', 'Start a new project', '/gsd:new-project'),
|
|
366
|
+
action('map-codebase', 'Map an existing codebase', '/gsd:map-codebase'),
|
|
367
|
+
action('quick', 'Quick task', '/gsd:quick'),
|
|
368
|
+
action('help', 'Show help', '/gsd:help'),
|
|
369
|
+
];
|
|
370
|
+
case 'paused':
|
|
371
|
+
return [
|
|
372
|
+
rec('resume-work', 'Resume work', '/gsd:resume-work'),
|
|
373
|
+
action('progress', 'Show progress', '/gsd:progress'),
|
|
374
|
+
action('quick', 'Quick task', '/gsd:quick'),
|
|
375
|
+
action('help', 'Show help', '/gsd:help'),
|
|
376
|
+
];
|
|
377
|
+
case 'blocked':
|
|
378
|
+
return [
|
|
379
|
+
rec('debug', 'Debug the blocker', '/gsd:debug'),
|
|
380
|
+
action('verify-work', 'Verify current work', '/gsd:verify-work'),
|
|
381
|
+
action('capture', 'Capture a note', '/gsd:capture'),
|
|
382
|
+
action('progress', 'Show progress', '/gsd:progress'),
|
|
383
|
+
];
|
|
384
|
+
case 'verify-failed':
|
|
385
|
+
return [
|
|
386
|
+
rec('verify-work', 'Re-verify work', '/gsd:verify-work'),
|
|
387
|
+
action('debug', 'Debug the failure', '/gsd:debug'),
|
|
388
|
+
action('code-review', 'Review recent work', '/gsd:code-review'),
|
|
389
|
+
action('progress', 'Show progress', '/gsd:progress'),
|
|
390
|
+
];
|
|
391
|
+
case 'needs-first-phase':
|
|
392
|
+
return [
|
|
393
|
+
rec('discuss-phase', 'Discuss the first phase', '/gsd:discuss-phase'),
|
|
394
|
+
action('plan-phase', 'Plan phase 1', '/gsd:plan-phase'),
|
|
395
|
+
action('quick', 'Quick task', '/gsd:quick'),
|
|
396
|
+
action('progress', 'Show progress', '/gsd:progress'),
|
|
397
|
+
];
|
|
398
|
+
case 'planning':
|
|
399
|
+
// Forward motion → delegate to the single gated engine (see 'executing').
|
|
400
|
+
return [
|
|
401
|
+
rec('progress-next', `Advance to the next step (plan phase ${phaseN || 1})`, '/gsd:progress --next'),
|
|
402
|
+
action('plan-phase', `Plan phase ${phaseN || 1}`, '/gsd:plan-phase'),
|
|
403
|
+
action('discuss-phase', 'Discuss before planning', '/gsd:discuss-phase'),
|
|
404
|
+
action('progress', 'Show progress', '/gsd:progress'),
|
|
405
|
+
];
|
|
406
|
+
case 'executing':
|
|
407
|
+
// In-project forward motion delegates to the single gated engine
|
|
408
|
+
// (/gsd:progress --next → workflows/next.md). Its Route 0 resume-incomplete
|
|
409
|
+
// -phase invariant + Gates 1-3 must not be bypassed by dispatching a raw
|
|
410
|
+
// /gsd:execute-phase here — that divergence is why the old flat /gsd-next
|
|
411
|
+
// was removed (#3054). Direct execute stays as an explicit secondary choice.
|
|
412
|
+
return [
|
|
413
|
+
rec('progress-next', 'Advance to the next step', '/gsd:progress --next'),
|
|
414
|
+
action('execute-phase', execLabel, '/gsd:execute-phase'),
|
|
415
|
+
action('quick', 'Quick task', '/gsd:quick'),
|
|
416
|
+
action('code-review', 'Review recent work', '/gsd:code-review'),
|
|
417
|
+
];
|
|
418
|
+
case 'verify-pending':
|
|
419
|
+
// Forward motion → delegate to the single gated engine (see 'executing').
|
|
420
|
+
return [
|
|
421
|
+
rec('progress-next', 'Advance to the next step (verify)', '/gsd:progress --next'),
|
|
422
|
+
action('verify-work', 'Verify work', '/gsd:verify-work'),
|
|
423
|
+
action('code-review', 'Review recent work', '/gsd:code-review'),
|
|
424
|
+
action('ship', 'Ship completed work', '/gsd:ship'),
|
|
425
|
+
];
|
|
426
|
+
case 'idle-stranded':
|
|
427
|
+
return [
|
|
428
|
+
rec('ship', 'Ship committed work', '/gsd:ship'),
|
|
429
|
+
action('complete-milestone', 'Complete the milestone', '/gsd:complete-milestone'),
|
|
430
|
+
action('progress', 'Show progress', '/gsd:progress'),
|
|
431
|
+
action('capture', 'Capture a note', '/gsd:capture'),
|
|
432
|
+
];
|
|
433
|
+
case 'complete':
|
|
434
|
+
return [
|
|
435
|
+
rec('new-milestone', 'Start a new milestone', '/gsd:new-milestone'),
|
|
436
|
+
action('extract-learnings', 'Extract learnings', '/gsd:extract-learnings'),
|
|
437
|
+
action('quick', 'Quick task', '/gsd:quick'),
|
|
438
|
+
action('progress', 'Show progress', '/gsd:progress'),
|
|
439
|
+
];
|
|
440
|
+
case 'unknown':
|
|
441
|
+
default:
|
|
442
|
+
return [
|
|
443
|
+
rec('progress', 'Show progress', '/gsd:progress'),
|
|
444
|
+
action('progress-next', 'Advance to the next step', '/gsd:progress --next'),
|
|
445
|
+
action('quick', 'Quick task', '/gsd:quick'),
|
|
446
|
+
action('help', 'Show help', '/gsd:help'),
|
|
447
|
+
];
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
// ─── Summary line ─────────────────────────────────────────────────────────────
|
|
451
|
+
function buildSummary(situation, s) {
|
|
452
|
+
switch (situation) {
|
|
453
|
+
case 'no-project':
|
|
454
|
+
return 'No project yet — start fresh or map an existing codebase';
|
|
455
|
+
case 'paused':
|
|
456
|
+
return 'Work is paused — pick up where you left off';
|
|
457
|
+
case 'blocked':
|
|
458
|
+
return `Blocked${s.blockers.length ? ` · ${s.blockers.length} blocker(s)` : ''} — resolve before continuing`;
|
|
459
|
+
case 'verify-failed':
|
|
460
|
+
return 'Recent verification failed — re-verify or debug';
|
|
461
|
+
case 'needs-first-phase':
|
|
462
|
+
return 'Project initialized — plan your first phase';
|
|
463
|
+
case 'planning':
|
|
464
|
+
return `Phase ${s.current_phase ?? '?'} of ${s.total_phases ?? '?'} — needs a plan`;
|
|
465
|
+
case 'executing':
|
|
466
|
+
return progressLine('executing', s);
|
|
467
|
+
case 'verify-pending':
|
|
468
|
+
return progressLine('ready to verify', s);
|
|
469
|
+
case 'idle-stranded':
|
|
470
|
+
return 'Committed work not shipped — time to ship';
|
|
471
|
+
case 'complete':
|
|
472
|
+
return 'All phases complete — start a new milestone';
|
|
473
|
+
case 'unknown':
|
|
474
|
+
default:
|
|
475
|
+
return 'Unsure of state — showing progress';
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
function progressLine(tail, s) {
|
|
479
|
+
const parts = [];
|
|
480
|
+
if (s.current_phase !== null && s.total_phases !== null) {
|
|
481
|
+
parts.push(`Phase ${s.current_phase} of ${s.total_phases}`);
|
|
482
|
+
}
|
|
483
|
+
else if (s.current_phase !== null) {
|
|
484
|
+
parts.push(`Phase ${s.current_phase}`);
|
|
485
|
+
}
|
|
486
|
+
if (s.progress !== null)
|
|
487
|
+
parts.push(`${s.progress}%`);
|
|
488
|
+
parts.push(tail);
|
|
489
|
+
return parts.join(' · ');
|
|
490
|
+
}
|
|
491
|
+
// ─── Orchestration ─────────────────────────────────────────────────────────────
|
|
492
|
+
/**
|
|
493
|
+
* Run the full detect → classify → assemble pipeline. Pure: no stdout, no
|
|
494
|
+
* writes. Exported for direct unit testing and reuse. The clock seam drives
|
|
495
|
+
* staleness detection inside detectSignals.
|
|
496
|
+
*/
|
|
497
|
+
function classifyProject(cwd, now = Date.now) {
|
|
498
|
+
const signals = detectSignals(cwd, now);
|
|
499
|
+
const situation = classify(signals);
|
|
500
|
+
const actions = actionsFor(situation, signals);
|
|
501
|
+
const recommended = actions.find((a) => a.recommended)?.id ?? 'progress';
|
|
502
|
+
const summary = buildSummary(situation, signals);
|
|
503
|
+
return { situation, recommended, summary, signals, actions };
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* `gsd-tools smart-entry` entry point. `--json` emits machine JSON; default
|
|
507
|
+
* emits a human-readable summary line. Never throws for a missing `.planning/`
|
|
508
|
+
* (returns `no-project`); other internal failures surface a typed error via
|
|
509
|
+
* `output()` so the workflow can fall back to `/gsd:progress`.
|
|
510
|
+
*/
|
|
511
|
+
function runSmartEntry(cwd, args, raw) {
|
|
512
|
+
const json = args.includes('--json');
|
|
513
|
+
const result = classifyProject(cwd);
|
|
514
|
+
if (json) {
|
|
515
|
+
output(result, raw, undefined);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
// Human mode: a compact one-liner plus the recommended action.
|
|
519
|
+
const human = `${result.summary}\nRecommended: ${result.recommended} → ${result.actions.find((a) => a.id === result.recommended)?.command ?? '/gsd:progress'}`;
|
|
520
|
+
output(null, true, human);
|
|
521
|
+
}
|
|
@@ -466,16 +466,25 @@ function _syncGsdDir(stagedDir, destDir, kind, manifest) {
|
|
|
466
466
|
// - agents: only gsd-* are GSD-owned
|
|
467
467
|
// - flat command dirs: only `${kindPrefix}`-prefixed are GSD-owned
|
|
468
468
|
// - namespaced command dirs: the whole dir is GSD-owned
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
469
|
+
//
|
|
470
|
+
// Manifest gate (#2018): when the manifest is empty/absent (e.g. an unresolvable
|
|
471
|
+
// install source root yields an empty staged dir), the staged set is untrustworthy.
|
|
472
|
+
// Skills are guarded by pruneSkillDirs' manifest-membership check; agents must be
|
|
473
|
+
// guarded here — skip the prune loop entirely so an empty manifest never deletes
|
|
474
|
+
// every gsd-* agent. Copying (above) still runs so genuinely new agents are added.
|
|
475
|
+
const shouldPruneAgents = !(kindName === 'agents' && (!manifest || manifest.size === 0));
|
|
476
|
+
if (shouldPruneAgents) {
|
|
477
|
+
for (const file of node_fs_1.default.readdirSync(destDir).filter(f => f.endsWith('.md'))) {
|
|
478
|
+
if (kindName === 'agents' && !file.startsWith('gsd-'))
|
|
479
|
+
continue;
|
|
480
|
+
if (kindName === 'commands' && !namespacedByDir && kindPrefix && !file.startsWith(kindPrefix))
|
|
481
|
+
continue;
|
|
482
|
+
if (!stagedDestNames.has(file)) {
|
|
483
|
+
try {
|
|
484
|
+
node_fs_1.default.unlinkSync(node_path_1.default.join(destDir, file));
|
|
485
|
+
}
|
|
486
|
+
catch { /* ignore */ }
|
|
477
487
|
}
|
|
478
|
-
catch { /* ignore */ }
|
|
479
488
|
}
|
|
480
489
|
}
|
|
481
490
|
}
|
|
@@ -33,7 +33,6 @@ exports.RUNTIME_DIRS = [
|
|
|
33
33
|
['antigravity', '.agent'], // local Antigravity install dir legacy (#503; backward-compat with pre-#791 installs)
|
|
34
34
|
['windsurf', '.windsurf'], // local Windsurf workflow dir canonical (#1615; bin/install.js getDirName('windsurf'))
|
|
35
35
|
['windsurf', '.devin'], // local Devin Desktop install dir legacy (#1085; backward-compat)
|
|
36
|
-
['gemini', '.gemini'],
|
|
37
36
|
['kilo', '.config/kilo'],
|
|
38
37
|
['kilo', '.kilo'],
|
|
39
38
|
['codex', '.codex'],
|
|
@@ -80,8 +79,6 @@ function inferPreferredRuntime({ fs, env, preferredConfigDir }) {
|
|
|
80
79
|
return 'codex';
|
|
81
80
|
if (env['ANTIGRAVITY_CONFIG_DIR'])
|
|
82
81
|
return 'antigravity';
|
|
83
|
-
if (env['GEMINI_CONFIG_DIR'])
|
|
84
|
-
return 'gemini';
|
|
85
82
|
if (env['KILO_CONFIG_DIR'] || env['KILO_CONFIG'])
|
|
86
83
|
return 'kilo';
|
|
87
84
|
if (env['OPENCODE_CONFIG_DIR'] || env['OPENCODE_CONFIG'])
|
|
@@ -98,8 +95,6 @@ function envRuntimeDirs({ env, home }) {
|
|
|
98
95
|
out.push(['claude', ex(env['CLAUDE_CONFIG_DIR'])]);
|
|
99
96
|
if (env['ANTIGRAVITY_CONFIG_DIR'])
|
|
100
97
|
out.push(['antigravity', ex(env['ANTIGRAVITY_CONFIG_DIR'])]);
|
|
101
|
-
if (env['GEMINI_CONFIG_DIR'])
|
|
102
|
-
out.push(['gemini', ex(env['GEMINI_CONFIG_DIR'])]);
|
|
103
98
|
if (env['KILO_CONFIG_DIR'])
|
|
104
99
|
out.push(['kilo', ex(env['KILO_CONFIG_DIR'])]);
|
|
105
100
|
else if (env['KILO_CONFIG'])
|
|
@@ -17,11 +17,6 @@
|
|
|
17
17
|
"sonnet": { "model": "gpt-5.4", "reasoning_effort": "medium" },
|
|
18
18
|
"haiku": { "model": "gpt-5.4-mini", "reasoning_effort": "medium" }
|
|
19
19
|
},
|
|
20
|
-
"gemini": {
|
|
21
|
-
"opus": { "model": "gemini-3.1-pro-preview" },
|
|
22
|
-
"sonnet": { "model": "gemini-3-flash" },
|
|
23
|
-
"haiku": { "model": "gemini-2.5-flash-lite" }
|
|
24
|
-
},
|
|
25
20
|
"qwen": {
|
|
26
21
|
"opus": { "model": "qwen3-max-2026-01-23" },
|
|
27
22
|
"sonnet": { "model": "qwen3-coder-plus" },
|
|
@@ -38,7 +38,7 @@ Canonical gate types used across GSD workflows. Every validation checkpoint maps
|
|
|
38
38
|
**Recovery:** Developer investigates root cause, fixes, restarts from checkpoint.
|
|
39
39
|
**Examples:**
|
|
40
40
|
- Context window critically low during execution
|
|
41
|
-
- STATE.md in error state blocking /gsd
|
|
41
|
+
- STATE.md in error state blocking /gsd:next
|
|
42
42
|
- Verification finds critical missing deliverables
|
|
43
43
|
|
|
44
44
|
---
|
|
@@ -317,7 +317,7 @@ Set via `features.*` namespace (e.g., `"features": { "thinking_partner": true }`
|
|
|
317
317
|
| Key | Type | Default | Allowed Values | Description |
|
|
318
318
|
|-----|------|---------|----------------|-------------|
|
|
319
319
|
| `features.thinking_partner` | boolean | `false` | `true`, `false` | Enable conditional extended thinking at workflow decision points (used by discuss-phase and plan-phase for architectural tradeoff analysis) |
|
|
320
|
-
| `features.global_learnings` | boolean | `false` | `true`, `false` | Enable injection of global learnings from `~/.gsd/
|
|
320
|
+
| `features.global_learnings` | boolean | `false` | `true`, `false` | Enable injection of global learnings from `~/.gsd/knowledge/` into agent prompts |
|
|
321
321
|
|
|
322
322
|
### Hook Fields
|
|
323
323
|
|