thoth-agents 0.3.9 → 0.3.10
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/dist/agents/prompt-dialects.d.ts +3 -0
- package/dist/{chunk-XEEYNMPC.js → chunk-JBG7XMIG.js} +1 -1
- package/dist/{chunk-2TLT6WWY.js → chunk-M2WF46BP.js} +39 -106
- package/dist/{chunk-MY2GIOOH.js → chunk-TKIDBRM3.js} +1 -1
- package/dist/cli/index.js +3 -3
- package/dist/cli/tui/index.js +2 -2
- package/dist/harness/core/sdd.d.ts +1 -1
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/plugin/.claude-plugin/.thoth-agents-plugin-assets.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/agents/orchestrator.md +17 -17
- package/plugin/skills/thoth-archive/SKILL.md +7 -0
- package/plugin/skills/thoth-archive/scripts/archive.mjs +28 -74
- package/plugin/skills/thoth-sdd/references/phases/specify.md +12 -1
- package/plugin/skills/thoth-sdd/scripts/durable-deltas.mjs +158 -0
- package/plugin/skills/thoth-sdd/scripts/validate.mjs +67 -28
- package/plugin/skills/thoth-sdd/templates/spec.md +9 -1
- package/skills/thoth-archive/SKILL.md +7 -0
- package/skills/thoth-archive/scripts/archive.mjs +28 -74
- package/skills/thoth-sdd/references/phases/specify.md +12 -1
- package/skills/thoth-sdd/scripts/durable-deltas.mjs +158 -0
- package/skills/thoth-sdd/scripts/validate.mjs +67 -28
- package/skills/thoth-sdd/templates/spec.md +9 -1
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import { existsSync, readFileSync } from 'node:fs';
|
|
4
4
|
import { join, posix, resolve } from 'node:path';
|
|
5
|
+
import {
|
|
6
|
+
CAPABILITY_PATTERN,
|
|
7
|
+
parseCanonicalSpec,
|
|
8
|
+
parseRequirementDelta,
|
|
9
|
+
preflightRequirementDeltas,
|
|
10
|
+
} from './durable-deltas.mjs';
|
|
5
11
|
|
|
6
12
|
function parseArgs(argv) {
|
|
7
13
|
const values = { route: 'full', through: 'ready', json: false };
|
|
@@ -86,34 +92,6 @@ function checkUniqueSequential(ids, prefix, artifact, errors) {
|
|
|
86
92
|
}
|
|
87
93
|
}
|
|
88
94
|
|
|
89
|
-
const CAPABILITY_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
90
|
-
|
|
91
|
-
function parseRequirementDelta(metadata) {
|
|
92
|
-
if (metadata === 'INTERNAL') {
|
|
93
|
-
return { operation: 'INTERNAL' };
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const change = /^(ADDED|MODIFIED|REMOVED)\s+(\S+)$/.exec(metadata);
|
|
97
|
-
if (change && CAPABILITY_PATTERN.test(change[2])) {
|
|
98
|
-
return { operation: change[1], capability: change[2] };
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const rename = /^RENAMED\s+(\S+)\s+FROM\s+(.+)$/.exec(metadata);
|
|
102
|
-
if (
|
|
103
|
-
rename &&
|
|
104
|
-
CAPABILITY_PATTERN.test(rename[1]) &&
|
|
105
|
-
rename[2].trim().length > 0
|
|
106
|
-
) {
|
|
107
|
-
return {
|
|
108
|
-
operation: 'RENAMED',
|
|
109
|
-
capability: rename[1],
|
|
110
|
-
previousTitle: rename[2].trim(),
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return undefined;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
95
|
function parseAffectedCapabilities(intentSection) {
|
|
118
96
|
const match = /^\*\*Affected capabilities\*\*:\s*(.+)$/im.exec(
|
|
119
97
|
intentSection ?? '',
|
|
@@ -264,6 +242,7 @@ function validateSpec(content, errors) {
|
|
|
264
242
|
),
|
|
265
243
|
);
|
|
266
244
|
}
|
|
245
|
+
const durableDeltas = [];
|
|
267
246
|
if (frIds.length === 0) {
|
|
268
247
|
errors.push(issue('SDD-SPEC-FR', artifact, 'Add FR-### requirements.'));
|
|
269
248
|
} else {
|
|
@@ -298,6 +277,11 @@ function validateSpec(content, errors) {
|
|
|
298
277
|
`FR-${match[1]} targets undeclared capability ${delta.capability}.`,
|
|
299
278
|
),
|
|
300
279
|
);
|
|
280
|
+
} else if (delta.operation !== 'INTERNAL') {
|
|
281
|
+
durableDeltas.push({
|
|
282
|
+
title: match[2].trim(),
|
|
283
|
+
...delta,
|
|
284
|
+
});
|
|
301
285
|
}
|
|
302
286
|
}
|
|
303
287
|
}
|
|
@@ -369,9 +353,56 @@ function validateSpec(content, errors) {
|
|
|
369
353
|
outcomeScIds: successCriteria
|
|
370
354
|
.filter((match) => match[2].toLowerCase() === 'outcome')
|
|
371
355
|
.map((match) => match[1]),
|
|
356
|
+
durableDeltas,
|
|
372
357
|
};
|
|
373
358
|
}
|
|
374
359
|
|
|
360
|
+
function validateDurableDeltaIntent(specRoot, deltas, errors, warnings) {
|
|
361
|
+
const byCapability = new Map();
|
|
362
|
+
for (const delta of deltas) {
|
|
363
|
+
const current = byCapability.get(delta.capability) ?? [];
|
|
364
|
+
current.push(delta);
|
|
365
|
+
byCapability.set(delta.capability, current);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
for (const [capability, capabilityDeltas] of byCapability) {
|
|
369
|
+
const path = join(specRoot, capability, 'spec.md');
|
|
370
|
+
const present = existsSync(path);
|
|
371
|
+
let canonical;
|
|
372
|
+
try {
|
|
373
|
+
canonical = present
|
|
374
|
+
? parseCanonicalSpec(readFileSync(path, 'utf8'))
|
|
375
|
+
: { requirements: new Map() };
|
|
376
|
+
} catch (error) {
|
|
377
|
+
errors.push(
|
|
378
|
+
issue(
|
|
379
|
+
'SDD-SPEC-DELTA-BASELINE',
|
|
380
|
+
'spec.md',
|
|
381
|
+
`${capability} canonical baseline is invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
382
|
+
),
|
|
383
|
+
);
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const result = preflightRequirementDeltas({
|
|
388
|
+
capability,
|
|
389
|
+
present,
|
|
390
|
+
requirements: canonical.requirements,
|
|
391
|
+
deltas: capabilityDeltas,
|
|
392
|
+
});
|
|
393
|
+
errors.push(
|
|
394
|
+
...result.errors.map((entry) =>
|
|
395
|
+
issue(entry.code, 'spec.md', entry.message),
|
|
396
|
+
),
|
|
397
|
+
);
|
|
398
|
+
warnings.push(
|
|
399
|
+
...result.warnings.map((entry) =>
|
|
400
|
+
issue(entry.code, 'spec.md', entry.message),
|
|
401
|
+
),
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
375
406
|
function constitutionEntries(content) {
|
|
376
407
|
if (content === undefined) return [];
|
|
377
408
|
return content
|
|
@@ -1047,6 +1078,14 @@ function validate({ change, route, through }) {
|
|
|
1047
1078
|
|
|
1048
1079
|
const specContract =
|
|
1049
1080
|
spec === undefined ? undefined : validateSpec(spec, errors);
|
|
1081
|
+
if (specContract !== undefined) {
|
|
1082
|
+
validateDurableDeltaIntent(
|
|
1083
|
+
join(root, '..', '..', 'specs'),
|
|
1084
|
+
specContract.durableDeltas,
|
|
1085
|
+
errors,
|
|
1086
|
+
warnings,
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1050
1089
|
const constitution = readArtifact(
|
|
1051
1090
|
join(root, '..', '..'),
|
|
1052
1091
|
'memory/constitution.md',
|
|
@@ -30,7 +30,15 @@ As a [actor], I can [capability] so that [value].
|
|
|
30
30
|
|
|
31
31
|
## Functional requirements
|
|
32
32
|
|
|
33
|
-
- **FR-001 — [Requirement name]**: `[
|
|
33
|
+
- **FR-001 — [Requirement name]**: `[DELTA capability-slug]` The system MUST [observable behavior].
|
|
34
|
+
|
|
35
|
+
Replace `DELTA` after inspecting `openspec/specs/<capability>/spec.md`. Use
|
|
36
|
+
`ADDED` only for behavior not expressed by a canonical requirement, `MODIFIED`
|
|
37
|
+
or `REMOVED` only with the exact existing requirement title, and
|
|
38
|
+
`RENAMED capability-slug FROM Exact previous title` when the title changes. Only
|
|
39
|
+
`ADDED` is valid when the canonical capability is absent. An addition to an
|
|
40
|
+
existing nonempty capability requires semantic-overlap review even when its
|
|
41
|
+
exact title is new.
|
|
34
42
|
|
|
35
43
|
Use exactly one delta marker per FR: `[INTERNAL]`, `[ADDED capability-slug]`,
|
|
36
44
|
`[MODIFIED capability-slug]`, `[REMOVED capability-slug]`, or
|