freeharness 0.4.1 → 0.4.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/NOTICE +2 -1
- package/README.md +24 -19
- package/canonical/HARVEST.yaml +27 -16
- package/canonical/PROVENANCE.yaml +3 -3
- package/canonical/agents/README.md +77 -36
- package/canonical/agents/agentic-architect.md +2 -1
- package/canonical/agents/database-reviewer.md +6 -8
- package/canonical/agents/explorer-ai-engineer-analyst.md +2 -6
- package/canonical/agents/implementer.md +30 -0
- package/canonical/agents/pr-reviewer.md +9 -11
- package/canonical/agents/python-reviewer.md +7 -9
- package/canonical/agents/report-formatter.md +7 -3
- package/canonical/agents/security-reviewer.md +14 -9
- package/canonical/agents/test-writer.md +5 -2
- package/canonical/hooks/cost-prices.json +11 -9
- package/canonical/hooks/freeharness-config-guard.mjs +4155 -93
- package/canonical/hooks/freeharness-context-monitor.mjs +88 -54
- package/canonical/hooks/freeharness-converge.mjs +78 -23
- package/canonical/hooks/freeharness-cost-tracker.mjs +1116 -218
- package/canonical/hooks/freeharness-memory-guard.mjs +20 -23
- package/canonical/hooks/freeharness-precompact.mjs +206 -87
- package/canonical/hooks/freeharness-quota-tap.mjs +173 -0
- package/canonical/hooks/freeharness-reload-context.mjs +197 -0
- package/canonical/hooks/freeharness-repeat-tool.mjs +1 -1
- package/canonical/hooks/freeharness-sdd-context.mjs +959 -0
- package/canonical/hooks/freeharness-session-start +297 -59
- package/canonical/hooks/hooks.claude-code.json +1 -1
- package/canonical/hooks/hooks.config-guard.claude-code.json +2 -2
- package/canonical/hooks/hooks.context-monitor.claude-code.json +1 -1
- package/canonical/hooks/hooks.converge.claude-code.json +1 -1
- package/canonical/hooks/hooks.memory-guard.claude-code.json +1 -1
- package/canonical/hooks/hooks.reload-context.claude-code.json +15 -0
- package/canonical/openspec/schema.spec-driven.yaml +29 -8
- package/canonical/openspec/templates/follow-ups.md +3 -2
- package/canonical/openspec/templates/proposal.md +1 -1
- package/canonical/openspec/templates/tasks.md +8 -0
- package/canonical/rules/CLAUDE.md +5 -4
- package/canonical/rules/routing.md +10 -9
- package/canonical/skills/agent-evaluation/SKILL.md +17 -6
- package/canonical/skills/agent-evaluation/evals/cases.yaml +38 -23
- package/canonical/skills/agentic-architecture/SKILL.md +4 -0
- package/canonical/skills/agentic-architecture/evals/cases.yaml +27 -0
- package/canonical/skills/code-review/SKILL.md +10 -3
- package/canonical/skills/code-review/evals/cases.yaml +1118 -27
- package/canonical/skills/git-workflow/SKILL.md +2 -0
- package/canonical/skills/local-code/SKILL.md +2 -2
- package/canonical/skills/local-code/evals/cases.yaml +1 -1
- package/canonical/skills/report-formatting/SKILL.md +10 -2
- package/canonical/skills/report-formatting/reference/ARCHITECTURE.md +3 -3
- package/canonical/skills/report-formatting/reference/HELPERS.md +10 -5
- package/canonical/skills/report-formatting/reference/RELEASE.md +1 -2
- package/canonical/skills/report-formatting/reference/TEMPLATES.md +3 -2
- package/canonical/skills/report-formatting/reference/authoring.md +25 -37
- package/canonical/skills/report-formatting/scripts/build-architecture.js +1 -1
- package/canonical/skills/report-formatting/scripts/build-md-claro.js +5 -8
- package/canonical/skills/report-formatting/scripts/check-env.js +0 -4
- package/canonical/skills/report-formatting/scripts/docmodel.js +3 -11
- package/canonical/skills/report-formatting/scripts/example-build.js +8 -10
- package/canonical/skills/report-formatting/scripts/example_build_ArchitectureReview_docx.js +8 -10
- package/canonical/skills/report-formatting/scripts/example_build_ExecutiveReport_docx.js +8 -10
- package/canonical/skills/report-formatting/scripts/theme-claro.js +39 -10
- package/canonical/skills/subagent-driven-development/SKILL.md +35 -28
- package/canonical/skills/subagent-driven-development/evals/cases.yaml +3625 -19
- package/canonical/skills/subagent-driven-development/reference/brief.md +187 -35
- package/canonical/skills/subagent-driven-development/reference/handoff.md +96 -2
- package/canonical/skills/subagent-driven-development/reference/loop.md +56 -0
- package/canonical/skills/subagent-driven-development/reference/trees.md +40 -0
- package/canonical/skills/subagent-driven-development/reference/workspace.md +31 -0
- package/canonical/skills/subagent-driven-development/scripts/validate-handoff.mjs +4035 -327
- package/dist/adapters/harness/claude-code.d.ts +26 -3
- package/dist/adapters/harness/claude-code.js +214 -43
- package/dist/adapters/harness/opencode.js +17 -3
- package/dist/adapters/harness/plugin.js +28 -17
- package/dist/adapters/memory/doctor.d.ts +172 -33
- package/dist/adapters/memory/doctor.js +750 -139
- package/dist/adapters/memory/migrate.d.ts +95 -0
- package/dist/adapters/memory/migrate.js +497 -0
- package/dist/adapters/memory/paths.d.ts +15 -0
- package/dist/adapters/memory/paths.js +47 -0
- package/dist/adapters/memory/vendored.d.ts +52 -30
- package/dist/adapters/memory/vendored.js +161 -169
- package/dist/adapters/memory/worker-control.d.ts +9 -0
- package/dist/adapters/memory/worker-control.js +30 -0
- package/dist/adapters/sync/diff.js +4 -1
- package/dist/cli/cleanup-prompt.d.ts +35 -0
- package/dist/cli/cleanup-prompt.js +68 -0
- package/dist/cli/cleanup.d.ts +22 -24
- package/dist/cli/cleanup.js +204 -75
- package/dist/cli/cost-quota.d.ts +158 -0
- package/dist/cli/cost-quota.js +645 -0
- package/dist/cli/cost.d.ts +408 -46
- package/dist/cli/cost.js +1731 -57
- package/dist/cli/doctor.d.ts +52 -13
- package/dist/cli/doctor.js +199 -45
- package/dist/cli/hard-cleanup.d.ts +93 -0
- package/dist/cli/hard-cleanup.js +552 -0
- package/dist/cli/install-facts.d.ts +29 -0
- package/dist/cli/install-facts.js +68 -0
- package/dist/cli/json-key-edit.d.ts +18 -0
- package/dist/cli/json-key-edit.js +118 -0
- package/dist/cli/memory-import.d.ts +74 -0
- package/dist/cli/memory-import.js +469 -0
- package/dist/cli/plugin-validate.d.ts +12 -0
- package/dist/cli/plugin-validate.js +43 -0
- package/dist/cli/profile.d.ts +1 -1
- package/dist/cli/profile.js +10 -10
- package/dist/cli/uninstall.d.ts +1 -1
- package/dist/cli/uninstall.js +2 -2
- package/dist/cli/upstream-launchers.d.ts +49 -0
- package/dist/cli/upstream-launchers.js +287 -0
- package/dist/index.js +252 -73
- package/dist/infra/fsutil.d.ts +30 -3
- package/dist/infra/fsutil.js +68 -6
- package/dist/infra/manifest.d.ts +2 -1
- package/dist/infra/manifest.js +3 -2
- package/dist/infra/prices.d.ts +18 -0
- package/dist/infra/prices.js +64 -0
- package/node_modules/@freeharness/core/dist/registry.d.ts +1 -1
- package/node_modules/@freeharness/core/dist/registry.js +3 -3
- package/node_modules/@freeharness/core/dist/tiers.d.ts +10 -1
- package/node_modules/@freeharness/core/dist/tiers.js +24 -2
- package/node_modules/@freeharness/core/package.json +1 -1
- package/node_modules/@freeharness/sdd/PROVENANCE.yaml +279 -2
- package/node_modules/@freeharness/sdd/dist/src/bin/openspec.js +34 -8
- package/node_modules/@freeharness/sdd/dist/src/shim/archive.d.ts +7 -6
- package/node_modules/@freeharness/sdd/dist/src/shim/archive.js +72 -64
- package/node_modules/@freeharness/sdd/dist/src/shim/follow-up.d.ts +48 -16
- package/node_modules/@freeharness/sdd/dist/src/shim/follow-up.js +168 -70
- package/node_modules/@freeharness/sdd/dist/src/shim/instructions.d.ts +36 -6
- package/node_modules/@freeharness/sdd/dist/src/shim/instructions.js +110 -49
- package/node_modules/@freeharness/sdd/dist/src/shim/list.d.ts +1 -1
- package/node_modules/@freeharness/sdd/dist/src/shim/list.js +57 -14
- package/node_modules/@freeharness/sdd/dist/src/shim/nested-change.d.ts +7 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/nested-change.js +118 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/new-change.js +3 -1
- package/node_modules/@freeharness/sdd/dist/src/shim/proposal-capabilities.d.ts +12 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/proposal-capabilities.js +94 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/shared.d.ts +17 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/shared.js +182 -1
- package/node_modules/@freeharness/sdd/dist/src/shim/status.d.ts +2 -2
- package/node_modules/@freeharness/sdd/dist/src/shim/status.js +34 -5
- package/node_modules/@freeharness/sdd/dist/src/shim/validate.d.ts +9 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/validate.js +49 -41
- package/node_modules/@freeharness/sdd/dist/src/shim/warnings.d.ts +21 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/warnings.js +66 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/apply/line-endings.d.ts +4 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/apply/line-endings.js +17 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/apply/specs-apply.js +95 -22
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/change-status-policy.d.ts +6 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/change-status-policy.js +14 -6
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/index.d.ts +1 -1
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/index.js +1 -1
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/outputs.d.ts +3 -3
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/outputs.js +24 -4
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/task-progress.js +4 -7
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/types.js +13 -1
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/apply-change.js +21 -6
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/archive-change.js +80 -19
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/explore.js +42 -22
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/project-root.d.ts +32 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/project-root.js +39 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/propose.js +16 -9
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/sync-specs.js +36 -1
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/update-change.js +65 -30
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/constants.d.ts +1 -1
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/constants.js +1 -1
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/change-parser.d.ts +8 -13
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/change-parser.js +79 -102
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/markdown-parser.js +4 -9
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/requirement-blocks.d.ts +50 -10
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/requirement-blocks.js +165 -37
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/requirement-text.d.ts +24 -3
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/requirement-text.js +55 -8
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/spec-structure.js +4 -1
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/purpose-placeholder.js +19 -9
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/spec-discovery.d.ts +10 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/spec-discovery.js +115 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/task-lines.d.ts +3 -3
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/task-lines.js +20 -20
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/validator.d.ts +6 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/validator.js +75 -14
- package/node_modules/@freeharness/sdd/package.json +1 -1
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/schema.upstream.yaml +47 -7
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-apply-change/SKILL.md +25 -7
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-archive-change/SKILL.md +50 -11
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-bulk-archive-change/SKILL.md +48 -10
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-continue-change/SKILL.md +27 -7
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-explore/SKILL.md +25 -12
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-ff-change/SKILL.md +17 -5
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-new-change/SKILL.md +12 -1
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-onboard/SKILL.md +47 -33
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-propose/SKILL.md +17 -5
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-sync-specs/SKILL.md +29 -1
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-update-change/SKILL.md +31 -13
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-verify-change/SKILL.md +75 -27
- package/package.json +6 -4
- package/provenance/canonical.PROVENANCE.yaml +3 -3
- package/provenance/memory.PROVENANCE.yaml +472 -6
- package/provenance/sdd.PROVENANCE.yaml +279 -2
- package/canonical/hooks/freeharness-auto-memory.mjs +0 -158
- package/canonical/hooks/hooks.auto-memory.claude-code.json +0 -16
- package/canonical/openspec/config.base.yaml +0 -89
- package/canonical/rules/safety.md +0 -41
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { promises as fs } from 'fs';
|
|
2
|
+
import * as fssync from 'fs';
|
|
2
3
|
import path from 'path';
|
|
3
4
|
import { FileSystemUtils } from '../engine/file-system.js';
|
|
5
|
+
import { parseDeltaSpec } from './parsers/requirement-blocks.js';
|
|
4
6
|
function assertDiscoveredSpecPath(specsRoot, capabilityDir, specFile) {
|
|
5
7
|
try {
|
|
6
8
|
FileSystemUtils.assertPathWithin(specsRoot, specFile);
|
|
@@ -112,3 +114,116 @@ export async function hasAnyFileUnder(dirPath) {
|
|
|
112
114
|
}
|
|
113
115
|
return false;
|
|
114
116
|
}
|
|
117
|
+
/** Async driver of walkUnreadDeltaFiles. */
|
|
118
|
+
export async function findUnreadDeltaFiles(specsRoot) {
|
|
119
|
+
const walker = walkUnreadDeltaFiles(specsRoot);
|
|
120
|
+
let step = walker.next();
|
|
121
|
+
while (!step.done) {
|
|
122
|
+
const request = step.value;
|
|
123
|
+
let reply;
|
|
124
|
+
try {
|
|
125
|
+
switch (request.op) {
|
|
126
|
+
case 'readdir':
|
|
127
|
+
reply = await fs.readdir(request.path, { withFileTypes: true });
|
|
128
|
+
break;
|
|
129
|
+
case 'stat':
|
|
130
|
+
reply = await fs.stat(request.path);
|
|
131
|
+
break;
|
|
132
|
+
case 'read':
|
|
133
|
+
reply = await fs.readFile(request.path, 'utf-8');
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
step = walker.throw(err);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
step = walker.next(reply);
|
|
142
|
+
}
|
|
143
|
+
return step.value;
|
|
144
|
+
}
|
|
145
|
+
/** Sync driver of walkUnreadDeltaFiles, for the instructions shim. */
|
|
146
|
+
export function findUnreadDeltaFilesSync(specsRoot) {
|
|
147
|
+
const walker = walkUnreadDeltaFiles(specsRoot);
|
|
148
|
+
let step = walker.next();
|
|
149
|
+
while (!step.done) {
|
|
150
|
+
const request = step.value;
|
|
151
|
+
let reply;
|
|
152
|
+
try {
|
|
153
|
+
switch (request.op) {
|
|
154
|
+
case 'readdir':
|
|
155
|
+
reply = fssync.readdirSync(request.path, { withFileTypes: true });
|
|
156
|
+
break;
|
|
157
|
+
case 'stat':
|
|
158
|
+
reply = fssync.statSync(request.path);
|
|
159
|
+
break;
|
|
160
|
+
case 'read':
|
|
161
|
+
reply = fssync.readFileSync(request.path, 'utf-8');
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
step = walker.throw(err);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
step = walker.next(reply);
|
|
170
|
+
}
|
|
171
|
+
return step.value;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Markdown files under a change's specs/ with delta sections that are not a
|
|
175
|
+
* capability's `spec.md` (`specs/user-auth.md`, or `specs/user-auth/delta.md`
|
|
176
|
+
* beside or instead of its spec.md). discoverSpecFiles, and so validate and
|
|
177
|
+
* archive, never reads them, while the artifact graph's specs/ markdown glob
|
|
178
|
+
* does: status and apply report the specs as written and archive has nothing
|
|
179
|
+
* to merge. A root `spec.md` has its own check; a note without delta sections
|
|
180
|
+
* is not reported. Like discoverSpecFiles, the walk skips dot entries and
|
|
181
|
+
* dangling links and never follows a symlinked directory. A missing root
|
|
182
|
+
* yields []; other read failures throw. Sorted by path. Filesystem calls are
|
|
183
|
+
* yielded to the async and sync drivers.
|
|
184
|
+
*/
|
|
185
|
+
function* walkUnreadDeltaFiles(specsRoot) {
|
|
186
|
+
const results = [];
|
|
187
|
+
function* walk(dir, segments) {
|
|
188
|
+
let entries;
|
|
189
|
+
try {
|
|
190
|
+
entries = (yield { op: 'readdir', path: dir });
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
const code = err?.code;
|
|
194
|
+
if (code === 'ENOENT' || code === 'ENOTDIR')
|
|
195
|
+
return;
|
|
196
|
+
throw err;
|
|
197
|
+
}
|
|
198
|
+
for (const entry of entries) {
|
|
199
|
+
if (entry.name.startsWith('.'))
|
|
200
|
+
continue;
|
|
201
|
+
if (entry.isDirectory()) {
|
|
202
|
+
yield* walk(path.join(dir, entry.name), [...segments, entry.name]);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (!entry.isFile() && !entry.isSymbolicLink())
|
|
206
|
+
continue;
|
|
207
|
+
if (entry.name === 'spec.md' || !entry.name.toLowerCase().endsWith('.md'))
|
|
208
|
+
continue;
|
|
209
|
+
const filePath = path.join(dir, entry.name);
|
|
210
|
+
let content;
|
|
211
|
+
try {
|
|
212
|
+
if (entry.isSymbolicLink() && !(yield { op: 'stat', path: filePath }).isFile())
|
|
213
|
+
continue;
|
|
214
|
+
content = (yield { op: 'read', path: filePath });
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
if (err?.code === 'ENOENT')
|
|
218
|
+
continue;
|
|
219
|
+
throw err;
|
|
220
|
+
}
|
|
221
|
+
if (!Object.values(parseDeltaSpec(content).sectionPresence).some(Boolean))
|
|
222
|
+
continue;
|
|
223
|
+
const capability = segments.length > 0 ? segments.join('/') : entry.name.slice(0, -'.md'.length);
|
|
224
|
+
results.push({ path: [...segments, entry.name].join('/'), expected: `${capability}/spec.md` });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
yield* walk(specsRoot, []);
|
|
228
|
+
return results.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
229
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export interface ParsedTask {
|
|
2
|
-
/** Checkbox state: `[x]`/`[X]` is done,
|
|
2
|
+
/** Checkbox state: `[x]`/`[X]` is done, every other marker (and none) is not. */
|
|
3
3
|
done: boolean;
|
|
4
4
|
/** Task text after the checkbox, trimmed (may be empty). */
|
|
5
5
|
description: string;
|
|
@@ -17,8 +17,8 @@ export type TaskAnnotationKey = (typeof TASK_ANNOTATION_KEYS)[number];
|
|
|
17
17
|
* A task resolved outside this change: `superseded`/`moved` name another
|
|
18
18
|
* change id, `external` carries free-form reference text. `kind` is the raw
|
|
19
19
|
* first token of the `Disposition:` value, so a value outside the three
|
|
20
|
-
* documented kinds still parses
|
|
21
|
-
*
|
|
20
|
+
* documented kinds still parses and `validate --strict` can report it instead
|
|
21
|
+
* of the line vanishing.
|
|
22
22
|
*/
|
|
23
23
|
export interface TaskDisposition {
|
|
24
24
|
kind: string;
|
|
@@ -4,16 +4,20 @@
|
|
|
4
4
|
// the single dependency `task-numbering.ts` has on task-progress. The rest of
|
|
5
5
|
// task-progress (schema/artifact-graph-driven task-file resolution, progress
|
|
6
6
|
// counting, unreadable-file tracking) is out of the change-validation slice and
|
|
7
|
-
// is deliberately NOT vendored. The
|
|
8
|
-
//
|
|
7
|
+
// is deliberately NOT vendored. The semantics below are faithful to upstream so
|
|
8
|
+
// task-numbering behaves identically.
|
|
9
9
|
/**
|
|
10
|
-
* A Markdown task line: a
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
10
|
+
* A Markdown task line: a checkbox list item under any CommonMark marker (`-`,
|
|
11
|
+
* `*`, `+`, or an ordered `1.`/`1)` of up to nine digits; #1862), nested or not.
|
|
12
|
+
* The box holds at most one non-bracket token (`[~]`, `[]`, `[ x]` all count,
|
|
13
|
+
* #1773); only `x`/`X` reads as done, so an unknown marker counts as open
|
|
14
|
+
* instead of vanishing. A `(` or `[` right after the box marks a link
|
|
15
|
+
* (`- [A](url)`), never a task, except after a whitespace-only box:
|
|
16
|
+
* `- [ ](...)` is open work. Unlike upstream, no two `\s*` runs flank an
|
|
17
|
+
* optional token, so matching stays linear. No end anchor: `.` does not match
|
|
18
|
+
* the `\r` of a CRLF file.
|
|
15
19
|
*/
|
|
16
|
-
const TASK_LINE_PATTERN = /^\s*[
|
|
20
|
+
const TASK_LINE_PATTERN = /^\s*(?:[-*+]|\d{1,9}[.)])\s*\[(?:\s*([^\]\s])\s*\](?![([])|\](?![([])|\s+\])\s*(.*)/;
|
|
17
21
|
/**
|
|
18
22
|
* Parses every task line in a tasks file, in document order. Every line matching
|
|
19
23
|
* the pattern counts, wherever it sits (inside a code fence, HTML comment, or an
|
|
@@ -24,18 +28,17 @@ export function parseTaskLines(content) {
|
|
|
24
28
|
for (const line of content.split('\n')) {
|
|
25
29
|
const match = line.match(TASK_LINE_PATTERN);
|
|
26
30
|
if (match) {
|
|
27
|
-
tasks.push({ done: match[1].toLowerCase() === 'x', description: match[2].trim() });
|
|
31
|
+
tasks.push({ done: (match[1] ?? '').toLowerCase() === 'x', description: match[2].trim() });
|
|
28
32
|
}
|
|
29
33
|
}
|
|
30
34
|
return tasks;
|
|
31
35
|
}
|
|
32
36
|
// ---------------------------------------------------------------------------
|
|
33
|
-
// FreeHarness
|
|
37
|
+
// FreeHarness: per-task annotation lines. Not in upstream.
|
|
34
38
|
//
|
|
35
|
-
// The FreeHarness tasks template allows optional lines under a task
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
// disposition:
|
|
39
|
+
// The FreeHarness tasks template allows optional lines under a task: its verify
|
|
40
|
+
// command, interfaces, write boundary, dependencies and, when the task is
|
|
41
|
+
// resolved outside this change, its disposition:
|
|
39
42
|
//
|
|
40
43
|
// - [ ] 2.1 Implement the parser
|
|
41
44
|
// Verify: node --test test/parser.test.ts
|
|
@@ -44,10 +47,8 @@ export function parseTaskLines(content) {
|
|
|
44
47
|
// Depends: 1.2
|
|
45
48
|
// Disposition: moved other-change
|
|
46
49
|
//
|
|
47
|
-
// They carry no checkbox, so `parseTaskLines`
|
|
48
|
-
//
|
|
49
|
-
// attaches the annotations to the task they follow for readers such as the
|
|
50
|
-
// converge hook and the subagent-driven-development briefs.
|
|
50
|
+
// They carry no checkbox, so `parseTaskLines` never counts them; this section
|
|
51
|
+
// defines the keys once and attaches each annotation to the task it follows.
|
|
51
52
|
// ---------------------------------------------------------------------------
|
|
52
53
|
/** The annotation keys the tasks template documents. */
|
|
53
54
|
export const TASK_ANNOTATION_KEYS = ['Verify', 'Interfaces', 'Boundary', 'Depends', 'Disposition'];
|
|
@@ -80,7 +81,7 @@ export function parseAnnotatedTaskLines(content) {
|
|
|
80
81
|
const task = line.match(TASK_LINE_PATTERN);
|
|
81
82
|
if (task) {
|
|
82
83
|
current = {
|
|
83
|
-
done: task[1].toLowerCase() === 'x',
|
|
84
|
+
done: (task[1] ?? '').toLowerCase() === 'x',
|
|
84
85
|
description: task[2].trim(),
|
|
85
86
|
line: i + 1,
|
|
86
87
|
annotations: {},
|
|
@@ -98,7 +99,6 @@ export function parseAnnotatedTaskLines(content) {
|
|
|
98
99
|
current.disposition = parseDisposition(value);
|
|
99
100
|
continue;
|
|
100
101
|
}
|
|
101
|
-
// A blank line or any other prose ends the annotation run.
|
|
102
102
|
current = undefined;
|
|
103
103
|
}
|
|
104
104
|
return tasks;
|
|
@@ -90,5 +90,11 @@ export declare class Validator {
|
|
|
90
90
|
*/
|
|
91
91
|
private buildMissingShallOrMustMessage;
|
|
92
92
|
private countScenarios;
|
|
93
|
+
/**
|
|
94
|
+
* Why a requirement with a visible scenario header still has no scenario:
|
|
95
|
+
* the header has no body, and the spec path archive validates against does
|
|
96
|
+
* not count it. Empty when the block has no bare scenario header.
|
|
97
|
+
*/
|
|
98
|
+
private emptyScenarioHint;
|
|
93
99
|
private formatSectionList;
|
|
94
100
|
}
|
|
@@ -4,11 +4,11 @@ import { SpecSchema, ChangeSchema } from './schemas/index.js';
|
|
|
4
4
|
import { MarkdownParser } from './parsers/markdown-parser.js';
|
|
5
5
|
import { ChangeParser } from './parsers/change-parser.js';
|
|
6
6
|
import { MIN_PURPOSE_LENGTH, MAX_REQUIREMENT_TEXT_LENGTH, VALIDATION_MESSAGES } from './constants.js';
|
|
7
|
-
import { parseDeltaSpec, foldRequirementName, normalizeRequirementName, extractRequirementsSection,
|
|
8
|
-
import { extractRequirementBody as extractRequirementBodyShared, containsShallOrMust as containsShallOrMustShared, countScenarios as countScenariosShared, } from './parsers/requirement-text.js';
|
|
7
|
+
import { parseDeltaSpec, foldRequirementName, normalizeRequirementName, extractRequirementsSection, diffScenarioNames, describeScenarioBalance, describeUnappliedContent, } from './parsers/requirement-blocks.js';
|
|
8
|
+
import { extractRequirementBody as extractRequirementBodyShared, containsShallOrMust as containsShallOrMustShared, countScenarios as countScenariosShared, countEmptyScenarios, } from './parsers/requirement-text.js';
|
|
9
9
|
import { findMainSpecStructureIssues } from './parsers/spec-structure.js';
|
|
10
10
|
import { FileSystemUtils } from '../engine/file-system.js';
|
|
11
|
-
import { discoverSpecFiles } from './spec-discovery.js';
|
|
11
|
+
import { discoverSpecFiles, findUnreadDeltaFiles } from './spec-discovery.js';
|
|
12
12
|
import { findTaskNumberingIssues } from './task-numbering.js';
|
|
13
13
|
import { findPurposePlaceholderIssue } from './purpose-placeholder.js';
|
|
14
14
|
import { findSpecUpdates, buildUpdatedSpec } from '../apply/specs-apply.js';
|
|
@@ -164,6 +164,38 @@ export class Validator {
|
|
|
164
164
|
: `Header "### ${stray.header}" in ${stray.section} is not a "### Requirement:" header and is ignored by validation. Use "### Requirement: ${stray.header}" if it should be validated as a requirement.`,
|
|
165
165
|
});
|
|
166
166
|
}
|
|
167
|
+
// A well-formed requirement outside every delta section, ignored by
|
|
168
|
+
// the reader. WARNING, not ERROR: a handful of pre-format archived
|
|
169
|
+
// changes still carry this shape, and the fix is to move the block.
|
|
170
|
+
for (const orphan of plan.orphanedRequirements) {
|
|
171
|
+
const where = orphan.section ? `under "## ${orphan.section}"` : 'above the first "## " section';
|
|
172
|
+
issues.push({
|
|
173
|
+
level: 'WARNING',
|
|
174
|
+
path: entryPath,
|
|
175
|
+
line: orphan.line,
|
|
176
|
+
message: `Requirement "${orphan.name}" is ${where}, which is not a delta section, so it is ignored. Move it under "## ADDED Requirements", "## MODIFIED Requirements", "## REMOVED Requirements", or "## RENAMED Requirements".`,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
// Archive carries requirement blocks only, by design. Warn so text
|
|
180
|
+
// written elsewhere is moved rather than lost without notice.
|
|
181
|
+
for (const lost of plan.unappliedContent) {
|
|
182
|
+
issues.push({
|
|
183
|
+
level: 'WARNING',
|
|
184
|
+
path: entryPath,
|
|
185
|
+
line: lost.line,
|
|
186
|
+
message: `${describeUnappliedContent(lost, true)} is not carried into the main spec by archive. Move it into a requirement, or into proposal.md or design.md.`,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
// A `TO:` line that never formed a pair, reported here so the author
|
|
190
|
+
// learns at authoring time rather than at archive.
|
|
191
|
+
for (const unpaired of plan.unpairedRenames) {
|
|
192
|
+
issues.push({
|
|
193
|
+
level: 'ERROR',
|
|
194
|
+
path: entryPath,
|
|
195
|
+
line: unpaired.line,
|
|
196
|
+
message: `RENAMED TO: "${unpaired.name}" has no matching FROM: line. Write each rename as a FROM: line followed immediately by its TO: line.`,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
167
199
|
const sectionNames = [];
|
|
168
200
|
if (plan.sectionPresence.added)
|
|
169
201
|
sectionNames.push('## ADDED Requirements');
|
|
@@ -215,7 +247,11 @@ export class Validator {
|
|
|
215
247
|
}
|
|
216
248
|
const scenarioCount = this.countScenarios(block.raw);
|
|
217
249
|
if (scenarioCount < 1) {
|
|
218
|
-
issues.push({
|
|
250
|
+
issues.push({
|
|
251
|
+
level: 'ERROR',
|
|
252
|
+
path: entryPath,
|
|
253
|
+
message: `ADDED "${block.name}" must include at least one scenario${this.emptyScenarioHint(block.raw)}`,
|
|
254
|
+
});
|
|
219
255
|
}
|
|
220
256
|
}
|
|
221
257
|
// Validate MODIFIED
|
|
@@ -247,7 +283,11 @@ export class Validator {
|
|
|
247
283
|
}
|
|
248
284
|
const scenarioCount = this.countScenarios(block.raw);
|
|
249
285
|
if (scenarioCount < 1) {
|
|
250
|
-
issues.push({
|
|
286
|
+
issues.push({
|
|
287
|
+
level: 'ERROR',
|
|
288
|
+
path: entryPath,
|
|
289
|
+
message: `MODIFIED "${block.name}" must include at least one scenario${this.emptyScenarioHint(block.raw)}`,
|
|
290
|
+
});
|
|
251
291
|
}
|
|
252
292
|
}
|
|
253
293
|
// Run archive's scenario-loss check here too, so the change fails at
|
|
@@ -349,6 +389,16 @@ export class Validator {
|
|
|
349
389
|
throw error;
|
|
350
390
|
}
|
|
351
391
|
}
|
|
392
|
+
// Same drop for delta sections in a file the merge path never reads
|
|
393
|
+
// (specs/<capability>.md), though the specs/**/*.md glob counts it written.
|
|
394
|
+
const unreadDeltaFiles = await findUnreadDeltaFiles(specsDir);
|
|
395
|
+
for (const file of unreadDeltaFiles) {
|
|
396
|
+
issues.push({
|
|
397
|
+
level: 'ERROR',
|
|
398
|
+
path: file.path,
|
|
399
|
+
message: `Delta spec found at specs/${file.path}. Delta specs must be a spec.md inside a capability folder — this file is ignored when the change is applied or archived. Move its requirements into specs/${file.expected}.`,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
352
402
|
for (const { path: specPath, sections } of emptySectionSpecs) {
|
|
353
403
|
issues.push({
|
|
354
404
|
level: 'ERROR',
|
|
@@ -363,11 +413,11 @@ export class Validator {
|
|
|
363
413
|
message: 'No delta sections found. Add headers such as "## ADDED Requirements" or move non-delta notes outside specs/.',
|
|
364
414
|
});
|
|
365
415
|
}
|
|
366
|
-
// The root-level
|
|
367
|
-
// deltas found" on top would contradict
|
|
368
|
-
// the
|
|
369
|
-
// vendored in this slice — see the file header.)
|
|
370
|
-
if (totalDeltas === 0 && !hasRootLevelSpec) {
|
|
416
|
+
// The root-level and unread-file errors already name the file and the fix;
|
|
417
|
+
// adding "No deltas found" on top would contradict them, since the deltas
|
|
418
|
+
// are sitting in the files just reported. (skip_specs marker handling is
|
|
419
|
+
// intentionally not vendored in this slice — see the file header.)
|
|
420
|
+
if (totalDeltas === 0 && !hasRootLevelSpec && unreadDeltaFiles.length === 0) {
|
|
371
421
|
issues.push({ level: 'ERROR', path: 'file', message: this.enrichTopLevelError('change', VALIDATION_MESSAGES.CHANGE_NO_DELTAS) });
|
|
372
422
|
}
|
|
373
423
|
if (options.projectRoot) {
|
|
@@ -473,15 +523,16 @@ export class Validator {
|
|
|
473
523
|
const current = currentBlockFor(key);
|
|
474
524
|
if (!current)
|
|
475
525
|
continue;
|
|
476
|
-
const
|
|
477
|
-
if (missing.length === 0)
|
|
526
|
+
const diff = diffScenarioNames(current, block);
|
|
527
|
+
if (diff.missing.length === 0)
|
|
478
528
|
continue;
|
|
479
529
|
issues.push({
|
|
480
530
|
level: 'ERROR',
|
|
481
531
|
path: entryPath,
|
|
482
532
|
message: `MODIFIED "${block.name}" omits scenario(s) the current spec still has: ` +
|
|
483
|
-
`${missing.map(name => `"${name}"`).join(', ')}. ` +
|
|
484
|
-
|
|
533
|
+
`${diff.missing.map(name => `"${name}"`).join(', ')}. ` +
|
|
534
|
+
`${describeScenarioBalance(diff)} ` +
|
|
535
|
+
'Copy the omitted scenarios into the MODIFIED block (a MODIFIED requirement replaces the whole block, so archive refuses to drop them).',
|
|
485
536
|
});
|
|
486
537
|
}
|
|
487
538
|
return issues;
|
|
@@ -744,6 +795,16 @@ export class Validator {
|
|
|
744
795
|
// example is not a real scenario. Drop the header line (index 0).
|
|
745
796
|
return countScenariosShared(blockRaw.split('\n').slice(1));
|
|
746
797
|
}
|
|
798
|
+
/**
|
|
799
|
+
* Why a requirement with a visible scenario header still has no scenario:
|
|
800
|
+
* the header has no body, and the spec path archive validates against does
|
|
801
|
+
* not count it. Empty when the block has no bare scenario header.
|
|
802
|
+
*/
|
|
803
|
+
emptyScenarioHint(blockRaw) {
|
|
804
|
+
if (countEmptyScenarios(blockRaw.split('\n').slice(1)) === 0)
|
|
805
|
+
return '';
|
|
806
|
+
return ' (a scenario header with no body under it does not count; add its steps, e.g. "- **WHEN** ..." and "- **THEN** ...")';
|
|
807
|
+
}
|
|
747
808
|
formatSectionList(sections) {
|
|
748
809
|
if (sections.length === 0)
|
|
749
810
|
return '';
|
|
@@ -13,7 +13,7 @@ artifacts:
|
|
|
13
13
|
- **Why**: 1-2 sentences on the problem or opportunity. What problem does this solve? Why now?
|
|
14
14
|
- **What Changes**: Bullet list of changes. Be specific about new capabilities, modifications, or removals. Mark breaking changes with **BREAKING**.
|
|
15
15
|
- **Capabilities**: Identify which specs will be created or modified:
|
|
16
|
-
- **New Capabilities**: List capabilities being introduced. Each becomes a new `specs/<capability-path>/spec.md`. Use kebab-case for path segments you introduce (e.g., `user-auth` or `identity/user-auth`) and follow the project's existing spec organization.
|
|
16
|
+
- **New Capabilities**: List capabilities being introduced. Each becomes a new `specs/<capability-path>/spec.md`. Name each capability for a durable system behavior (for example, `user-auth`), not the work in this change (for example, `add-login-endpoint`). Choose a cohesive boundary that can own related requirements as the system evolves; avoid broad catch-all capabilities. Use kebab-case for path segments you introduce (e.g., `user-auth` or `identity/user-auth`) and follow the project's existing spec organization.
|
|
17
17
|
- **Modified Capabilities**: List existing capabilities whose REQUIREMENTS are changing. Only include if spec-level behavior changes (not just implementation details). Each needs a delta spec file. Use the exact existing path under `openspec/specs/`. Leave empty if no requirement changes.
|
|
18
18
|
- **Impact**: Affected code, APIs, dependencies, or systems.
|
|
19
19
|
|
|
@@ -77,6 +77,17 @@ artifacts:
|
|
|
77
77
|
- New capabilities: use the exact path from the proposal at `specs/<capability-path>/spec.md`. Any path segment newly introduced in the proposal must be kebab-case. Follow the project's existing organization; do not add a new domain level when the project uses a flat layout.
|
|
78
78
|
- Modified capabilities: use the exact existing path from `openspec/specs/<capability-path>/` when creating the delta at `specs/<capability-path>/spec.md`. Run `openspec list --specs` to confirm that path before writing the delta, appending `--store "<id>"` only for a registered standalone store - a mistyped or invented path targets a capability that does not exist rather than the one you meant. Do not move or rename the capability.
|
|
79
79
|
|
|
80
|
+
Write the spec files one at a time, so the user sees progress and an
|
|
81
|
+
interrupted run keeps the files it already wrote:
|
|
82
|
+
- Before writing anything, compare the capabilities the proposal lists
|
|
83
|
+
with the `specs/<capability-path>/spec.md` files that already exist,
|
|
84
|
+
and start with the first capability that has no file. Do not rewrite
|
|
85
|
+
an existing file unless the user asks you to.
|
|
86
|
+
- Before each file, say in one line which capability you are writing.
|
|
87
|
+
- Write each file as soon as you have drafted it. Do not plan every file
|
|
88
|
+
before writing the first.
|
|
89
|
+
- Stop when every capability the proposal lists has its file.
|
|
90
|
+
|
|
80
91
|
There must be at least one spec file unless the change's `.openspec.yaml`
|
|
81
92
|
sets `skip_specs: true` (no spec-level behavior change) - `openspec validate`
|
|
82
93
|
rejects a zero-delta change without that marker. If the proposal lists no
|
|
@@ -94,8 +105,9 @@ artifacts:
|
|
|
94
105
|
- Each scenario: `#### Scenario: <name>` with WHEN/THEN format
|
|
95
106
|
- **CRITICAL**: Scenarios MUST use exactly 4 hashtags (`####`). Using 3 hashtags or bullets will fail silently.
|
|
96
107
|
- Every requirement MUST have at least one scenario.
|
|
108
|
+
- Keep each requirement's description (the text between `### Requirement:` and its first scenario) to 500 characters or fewer. `openspec validate` flags longer descriptions in ADDED requirements and in the main spec. This is a warning: normal validation still passes, but `openspec validate --strict` fails on it. When writing a new requirement, state one behavior per requirement: move examples and edge cases into scenarios, and split a requirement that covers several behaviors into separate `### Requirement:` blocks, each with its own scenarios. Under MODIFIED, keep the existing requirement block whole; never split, trim or rewrite existing text just to meet the length. Split an existing long requirement only when the user asks for it, in a change made for that purpose: under MODIFIED, keep its header and every scenario and cut its description down to one behavior without changing its meaning, then add each behavior you removed as its own ADDED requirement with its own scenarios.
|
|
97
109
|
|
|
98
|
-
New capabilities only:
|
|
110
|
+
New capabilities only: the delta spec's first section is `## Purpose` -
|
|
99
111
|
one or two sentences (50+ characters, or `openspec validate --strict`
|
|
100
112
|
reports it as too brief) describing what the capability is for. Archive
|
|
101
113
|
copies it into the main spec it creates; without it the new main spec is
|
|
@@ -120,8 +132,10 @@ artifacts:
|
|
|
120
132
|
Common pitfall: Using MODIFIED with partial content loses detail at archive time.
|
|
121
133
|
If adding new concerns without changing existing behavior, use ADDED instead.
|
|
122
134
|
|
|
123
|
-
Example (a new capability, so
|
|
135
|
+
Example (a new capability, so its first section is `## Purpose`):
|
|
124
136
|
```
|
|
137
|
+
# Spec Delta
|
|
138
|
+
|
|
125
139
|
## Purpose
|
|
126
140
|
|
|
127
141
|
Lets users take their data out of the product in a portable format.
|
|
@@ -192,22 +206,39 @@ artifacts:
|
|
|
192
206
|
would change what gets built, resolve them with the user first - do not
|
|
193
207
|
bake an unstated assumption into the task list.
|
|
194
208
|
|
|
195
|
-
**IMPORTANT: Follow the template below
|
|
196
|
-
checkbox format to track progress.
|
|
209
|
+
**IMPORTANT: Follow the template below for tracked tasks.** The apply phase parses
|
|
210
|
+
checkbox format to track progress. A box holding only `x` counts as done,
|
|
211
|
+
upper or lower case and with any spacing, so `- [ x]` is done too. Every
|
|
212
|
+
other marker, including `- [~]`, `- [-]` and an empty `- []`, reads as
|
|
213
|
+
unfinished. A line with no checkbox is not tracked at all.
|
|
197
214
|
|
|
198
215
|
Guidelines:
|
|
199
|
-
- Group related tasks under ## numbered headings
|
|
200
|
-
- Each task MUST be a checkbox: `- [ ] X.Y Task description`
|
|
216
|
+
- Group related tracked tasks under ## numbered headings
|
|
217
|
+
- Each tracked task MUST be a checkbox: `- [ ] X.Y Task description`
|
|
201
218
|
- Tasks should be small enough to complete in one session
|
|
202
219
|
- Order tasks by dependency (what must be done first?)
|
|
220
|
+
- Track implementation and verification work that can be completed before
|
|
221
|
+
archive. If the requested workflow includes archive or work that requires
|
|
222
|
+
this change to be archived, preserve those steps as plain bullets in an
|
|
223
|
+
optional `## Workflow follow-up` section at the end of tasks.md. These
|
|
224
|
+
bullets are reference information outside tracked task progress.
|
|
203
225
|
- Each task MUST state how to verify completion (a test, command,
|
|
204
226
|
observable behavior, or delivered artifact). Put the verification in
|
|
205
227
|
that task's checkbox description. Use a separate verification task only
|
|
206
228
|
when it checks broader integration or system behavior that spans
|
|
207
229
|
multiple implementation tasks.
|
|
230
|
+
- Each task group MUST land the tests and documentation its own work
|
|
231
|
+
calls for. Do NOT collect testing or documentation into a final group -
|
|
232
|
+
when a late group first exercises work from an early one, the failures
|
|
233
|
+
cascade back through every group in between and force rework. A group
|
|
234
|
+
whose work calls for neither, such as scaffolding or dependency setup,
|
|
235
|
+
carries neither. A final group is for integration checks only, not for
|
|
236
|
+
the tests and docs an earlier group owed.
|
|
208
237
|
|
|
209
238
|
Example:
|
|
210
239
|
```
|
|
240
|
+
# Tasks
|
|
241
|
+
|
|
211
242
|
## 1. Setup
|
|
212
243
|
|
|
213
244
|
- [ ] 1.1 Create new module structure and verify expected files are present
|
|
@@ -217,6 +248,15 @@ artifacts:
|
|
|
217
248
|
|
|
218
249
|
- [ ] 2.1 Implement data export function and verify the export test passes
|
|
219
250
|
- [ ] 2.2 Add CSV formatting utilities and verify unit tests cover quoting and delimiters
|
|
251
|
+
- [ ] 2.3 Document the export API in docs/export.md and verify the documented command runs as written
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
When applicable, append workflow follow-up as plain bullets, for example:
|
|
255
|
+
```
|
|
256
|
+
## Workflow follow-up
|
|
257
|
+
|
|
258
|
+
- Archive the change after the project's review requirements are satisfied.
|
|
259
|
+
- Verify the archived result.
|
|
220
260
|
```
|
|
221
261
|
|
|
222
262
|
Reference specs for what needs to be built, design for how to build it.
|
package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-apply-change/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: openspec-apply-change
|
|
3
|
-
description: Implement tasks from an OpenSpec change. Use when the user wants to start implementing, continue implementation, or work through tasks.
|
|
3
|
+
description: Implement tasks from an OpenSpec change. Use when the user wants to start implementing, continue implementation, or work through tasks. Also use when the user says "openspec apply", "opsx apply", or "openspec implement".
|
|
4
4
|
allowed-tools: Bash(openspec:*)
|
|
5
5
|
license: MIT
|
|
6
6
|
compatibility: Requires openspec CLI.
|
|
@@ -13,6 +13,17 @@ Implement tasks from an OpenSpec change.
|
|
|
13
13
|
|
|
14
14
|
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`, `schemas`, `view`). Once selected, treat `--store <id>` as sticky for the rest of the workflow. Every unscoped example of those commands below is shorthand: before running it, append the flag. For example, run `openspec status --change "<name>" --json --store "<id>"`, not the unscoped form shown below. Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
|
|
15
15
|
|
|
16
|
+
**Project check:** These steps expect a project that already uses OpenSpec. Before the first step that writes anything (`new change`, `archive`, `sync specs`, or authoring an artifact file), confirm the project has a root: run `openspec list --json` (with `--store <id>` when a store is selected, since the store is then the root) and read `root`. A root object means the project is set up. `"root": null` means it is not - there is no `openspec/` directory here, and a write such as `openspec new change` would create one as a side effect. The command also exits non-zero, which is that answer rather than a broken CLI, so read the JSON instead of retrying or working around it.
|
|
17
|
+
|
|
18
|
+
One `"root": null` is not about setup: when a `status` error message starts with `Declared in` or `Invalid store declaration in` and names this project's `openspec/config.yaml` (or `config.yml`), the project does use OpenSpec through a store it declares, which this machine cannot resolve (the store is not registered, or the `store:` line is malformed). Do not treat it as uninitialized and skip the branches below: stop before writing and show the user that error's `message` and `fix`.
|
|
19
|
+
|
|
20
|
+
Otherwise, with no root, what happens next depends on how this workflow was reached:
|
|
21
|
+
|
|
22
|
+
- **Auto-selected**: you chose this workflow yourself, without the user naming OpenSpec, naming this skill, or running its slash command. Stop using OpenSpec and answer the request normally, as you would with no OpenSpec installed. Do not ask them to set anything up and do not mention OpenSpec setup.
|
|
23
|
+
- **Explicit OpenSpec request**: the user named OpenSpec, named this skill, or ran its slash command. Stop before writing and ask how to proceed: set this project up (`openspec init`), target a store they already have (`--store <id>`), or continue without OpenSpec for this request. Wait for their answer.
|
|
24
|
+
|
|
25
|
+
In both branches, never create the root as a side effect: do not run `openspec init` until the user asks for it, do not hand-create `openspec/` files, and do not let a command create it.
|
|
26
|
+
|
|
16
27
|
**Input**: Optionally specify a change name (e.g., `/openspec-apply-change add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
|
17
28
|
|
|
18
29
|
**Steps**
|
|
@@ -44,14 +55,17 @@ Implement tasks from an OpenSpec change.
|
|
|
44
55
|
This returns:
|
|
45
56
|
- `contextFiles`: artifact ID -> array of concrete file paths (varies by schema - could be proposal/specs/design/tasks or spec/tests/implementation/docs)
|
|
46
57
|
- Progress (total, complete, remaining)
|
|
47
|
-
- Task list with status
|
|
58
|
+
- Task list with status, source path, and source line
|
|
48
59
|
- Dynamic instruction based on current state
|
|
49
60
|
- Optional `context`: current required project instruction input from the selected root
|
|
50
61
|
- Optional `operationGuidance`: current advisory guidance for apply
|
|
62
|
+
- `missingArtifacts` (when present): required artifact ids with no output
|
|
51
63
|
|
|
52
64
|
**Handle states:**
|
|
53
|
-
- If `state: "blocked"
|
|
54
|
-
|
|
65
|
+
- If `state: "blocked"`: show the message and pause implementation.
|
|
66
|
+
- If `missingArtifacts` is non-empty: suggest using `/openspec-continue-change` to create them.
|
|
67
|
+
- Otherwise, follow the CLI instruction to create or repair the schema-configured tracking file from existing planning artifacts. Do not assume another artifact is ready or start implementation while blocked.
|
|
68
|
+
- If `state: "all_done"`: report that all tracked tasks are complete and suggest review or verification as appropriate before archiving
|
|
55
69
|
- Otherwise: proceed to implementation
|
|
56
70
|
|
|
57
71
|
Treat `context` as a required prompt-level input. Read and consider it, and
|
|
@@ -93,7 +107,9 @@ Implement tasks from an OpenSpec change.
|
|
|
93
107
|
- Show which task is being worked on
|
|
94
108
|
- Make the code changes required
|
|
95
109
|
- Keep changes minimal and focused
|
|
96
|
-
-
|
|
110
|
+
- Before editing, confirm the checkbox at the returned `sourcePath` and `line` still matches the task description; if it does not, rerun the apply instructions and use the refreshed location
|
|
111
|
+
- Mark the task complete at its returned `sourcePath` and `line`: `- [ ]` → `- [x]`
|
|
112
|
+
- Rerun the apply instructions and confirm that task is now done and progress changed
|
|
97
113
|
- Continue to next task
|
|
98
114
|
|
|
99
115
|
**Pause if:**
|
|
@@ -108,7 +124,7 @@ Implement tasks from an OpenSpec change.
|
|
|
108
124
|
Display:
|
|
109
125
|
- Tasks completed this session
|
|
110
126
|
- Overall progress: "N/M tasks complete"
|
|
111
|
-
- If all done: suggest
|
|
127
|
+
- If all done: report that tracked tasks are complete and suggest review or verification as appropriate before archiving
|
|
112
128
|
- If paused: explain why and wait for guidance
|
|
113
129
|
|
|
114
130
|
**Output During Implementation**
|
|
@@ -139,7 +155,8 @@ Working on task 4/7: <task description>
|
|
|
139
155
|
- [x] Task 2
|
|
140
156
|
...
|
|
141
157
|
|
|
142
|
-
All tasks complete
|
|
158
|
+
All tracked tasks are complete. Review or verify the change as appropriate
|
|
159
|
+
before archiving. You can archive this change with `/openspec-archive-change`.
|
|
143
160
|
```
|
|
144
161
|
|
|
145
162
|
**Output On Pause (Issue Encountered)**
|
|
@@ -173,6 +190,7 @@ What would you like to do?
|
|
|
173
190
|
- When a task needs work beyond what the spec describes, surface the added scope and pause - never silently narrow, defer, or simplify away specified behavior
|
|
174
191
|
- Only mark a task `- [x]` when its specified behavior is fully implemented, not when it is partially done or deferred
|
|
175
192
|
- Use contextFiles from CLI output, don't assume specific file names
|
|
193
|
+
- Use each task's sourcePath and line to update its exact checkbox
|
|
176
194
|
- Do not use context or operation guidance as proof that a task is complete
|
|
177
195
|
- Apply relevant project context; report conflicts with controlling workflow inputs
|
|
178
196
|
- Consider every guidance entry; explain any inapplicable or conflicting advice
|