@planu/cli 4.3.24 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/engine/constitution/sdd-rules-registry.d.ts +12 -0
- package/dist/engine/constitution/sdd-rules-registry.js +105 -0
- package/dist/engine/evidence-index/done-drift.d.ts +6 -0
- package/dist/engine/evidence-index/done-drift.js +44 -0
- package/dist/engine/evidence-index/index-builder.d.ts +10 -0
- package/dist/engine/evidence-index/index-builder.js +138 -0
- package/dist/engine/handoff-artifacts/index.d.ts +1 -16
- package/dist/engine/handoff-artifacts/io.d.ts +1 -1
- package/dist/engine/handoff-artifacts/schemas.d.ts +1 -1
- package/dist/engine/handoff-artifacts/validation-result.d.ts +18 -0
- package/dist/engine/handoff-artifacts/validation-result.js +3 -0
- package/dist/engine/hooks/handlers/on-impl-change.js +3 -2
- package/dist/engine/host-rules-templates/templates.js +3 -0
- package/dist/engine/spec-format/lean-spec-generator.js +17 -12
- package/dist/engine/spec-grounding/contract.d.ts +19 -0
- package/dist/engine/spec-grounding/contract.js +186 -0
- package/dist/engine/spec-metrics/actionable-metrics.d.ts +10 -0
- package/dist/engine/spec-metrics/actionable-metrics.js +69 -0
- package/dist/engine/spec-quality/generic-output-gate.d.ts +3 -0
- package/dist/engine/spec-quality/generic-output-gate.js +105 -0
- package/dist/engine/test-contract-generator.js +41 -39
- package/dist/engine/test-scaffold-generator/unit-scaffold.js +10 -10
- package/dist/engine/test-spec-generator/criterion-parser.js +5 -24
- package/dist/storage/base-store.d.ts +0 -1
- package/dist/storage/base-store.js +0 -4
- package/dist/tools/create-spec.js +160 -30
- package/dist/tools/generate-tests/generators/concurrency-test-generator/java-templates.js +8 -13
- package/dist/tools/generate-tests/generators/concurrency-test-generator/js-templates.js +21 -47
- package/dist/tools/generate-tests/generators/concurrency-test-generator/python-rust-templates.js +3 -14
- package/dist/tools/git/auto-complete-ops.d.ts +18 -0
- package/dist/tools/git/auto-complete-ops.js +63 -0
- package/dist/tools/git/branch-ops.d.ts +0 -17
- package/dist/tools/git/branch-ops.js +0 -54
- package/dist/tools/git/cleanup-ops.js +0 -16
- package/dist/tools/git/release-ops.js +1 -1
- package/dist/tools/init-project/handler.js +1 -1
- package/dist/tools/manage-hooks.js +3 -1
- package/dist/tools/status-handler.js +1 -1
- package/dist/tools/update-status/evidence-gate.js +23 -6
- package/dist/tools/update-status/transition-guard.js +49 -0
- package/dist/tools/update-status-actions.js +8 -5
- package/dist/types/actionable-spec-metrics.d.ts +8 -0
- package/dist/types/actionable-spec-metrics.js +2 -0
- package/dist/types/clarification-token.d.ts +1 -1
- package/dist/types/clarification.d.ts +2 -18
- package/dist/types/evidence-gates.d.ts +1 -1
- package/dist/types/evidence-index.d.ts +24 -0
- package/dist/types/evidence-index.js +2 -0
- package/dist/types/hook-status-update.d.ts +10 -0
- package/dist/types/hook-status-update.js +3 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.js +6 -0
- package/dist/types/interactive-question.d.ts +19 -0
- package/dist/types/interactive-question.js +3 -0
- package/dist/types/sdd-constitution.d.ts +27 -0
- package/dist/types/sdd-constitution.js +2 -0
- package/dist/types/spec-format.d.ts +7 -0
- package/dist/types/spec-grounding.d.ts +22 -0
- package/dist/types/spec-grounding.js +2 -0
- package/dist/types/spec-quality.d.ts +10 -0
- package/dist/types/storage.d.ts +1 -1
- package/package.json +9 -9
- package/planu-native.json +1 -1
- package/planu-plugin.json +1 -1
- package/dist/engine/hooks/index.d.ts +0 -20
- package/dist/engine/hooks/index.js +0 -25
- package/dist/storage/crud-store-factory.d.ts +0 -22
- package/dist/storage/crud-store-factory.js +0 -72
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { specStore } from '../../storage/index.js';
|
|
2
|
-
import { handleUpdateStatus } from '../update-status/index.js';
|
|
3
2
|
import { ti } from '../../i18n/index.js';
|
|
4
3
|
import { git, resolveProjectPath, slugify, mergeConfig, detectGitFlowType, resolveBaseBranch, DEFAULT_BRANCH_PREFIXES, DEFAULT_PROTECTED_BRANCHES, } from './git-helpers.js';
|
|
5
4
|
import { compactResult, compactError, formatKeyValue, formatTable, formatList, } from '../output-formatter.js';
|
|
@@ -20,59 +19,6 @@ function randomSuffix() {
|
|
|
20
19
|
.toString(16)
|
|
21
20
|
.padStart(8, '0');
|
|
22
21
|
}
|
|
23
|
-
export async function autoCompleteSpecs(projectId, projectPath) {
|
|
24
|
-
// Try develop first, fall back to main
|
|
25
|
-
let mergedOut;
|
|
26
|
-
try {
|
|
27
|
-
const r = await git(projectPath, ['branch', '--merged', 'develop']);
|
|
28
|
-
mergedOut = r.stdout;
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
const r = await git(projectPath, ['branch', '--merged', 'main']);
|
|
32
|
-
mergedOut = r.stdout;
|
|
33
|
-
}
|
|
34
|
-
const mergedList = mergedOut
|
|
35
|
-
.split('\n')
|
|
36
|
-
.map((b) => b.replace(/^\*?\s+/, '').trim())
|
|
37
|
-
.filter(Boolean);
|
|
38
|
-
const mergedSet = new Set(mergedList);
|
|
39
|
-
/** Returns true if the spec's branch (exact or by ID pattern) appears in merged branches. */
|
|
40
|
-
function isMerged(specId, gitBranch) {
|
|
41
|
-
if (gitBranch && mergedSet.has(gitBranch)) {
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
44
|
-
const num = /SPEC-(\d+)/i.exec(specId)?.[1];
|
|
45
|
-
if (!num) {
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
const pat = new RegExp(`[/\\-]SPEC-${num}([/\\-]|$)`, 'i');
|
|
49
|
-
return mergedList.some((b) => pat.test(b));
|
|
50
|
-
}
|
|
51
|
-
const specs = (await specStore.listSpecs(projectId)).filter((s) => (s.status === 'implementing' || s.status === 'approved') && isMerged(s.id, s.gitBranch));
|
|
52
|
-
const completed = [];
|
|
53
|
-
const blocked = [];
|
|
54
|
-
for (const spec of specs) {
|
|
55
|
-
// SPEC-720: Route through handleUpdateStatus so all gates (DoD/validate/QA) run.
|
|
56
|
-
// Never call specStore.updateSpec({status}) directly.
|
|
57
|
-
const result = await handleUpdateStatus({
|
|
58
|
-
specId: spec.id,
|
|
59
|
-
status: 'done',
|
|
60
|
-
projectId,
|
|
61
|
-
projectPath,
|
|
62
|
-
trigger: 'git-merge',
|
|
63
|
-
actor: 'system',
|
|
64
|
-
reviewNotes: 'Auto-completed: branch merged',
|
|
65
|
-
});
|
|
66
|
-
if (result.isError) {
|
|
67
|
-
const reason = result.content[0]?.type === 'text' ? result.content[0].text : 'gate blocked (no message)';
|
|
68
|
-
blocked.push({ specId: spec.id, reason });
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
completed.push(spec.id);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return { completed, blocked };
|
|
75
|
-
}
|
|
76
22
|
export async function handleCreateBranch(projectId, specId, config) {
|
|
77
23
|
if (!specId) {
|
|
78
24
|
return compactError('❌ Error: specId is required for create-branch action');
|
|
@@ -3,7 +3,6 @@ import { existsSync } from 'node:fs';
|
|
|
3
3
|
import { readdir } from 'node:fs/promises';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { git, resolveProjectPath } from './git-helpers.js';
|
|
6
|
-
import { autoCompleteSpecs } from './branch-ops.js';
|
|
7
6
|
import { compactResult, compactError } from '../output-formatter.js';
|
|
8
7
|
const WORKTREES_DIR = '.claude/worktrees';
|
|
9
8
|
const STALE_DAYS_THRESHOLD = 7;
|
|
@@ -283,23 +282,12 @@ export async function handleCleanup(projectId, config) {
|
|
|
283
282
|
stashesDropped = dropped;
|
|
284
283
|
errors.push(...stashErrors);
|
|
285
284
|
}
|
|
286
|
-
// 6. Auto-complete implementing specs whose branch is merged to develop (SPEC-397)
|
|
287
|
-
// SPEC-720: autoCompleteSpecs now returns {completed, blocked}
|
|
288
|
-
const autoCompleteResult = await autoCompleteSpecs(projectId, projectPath).catch(() => ({
|
|
289
|
-
completed: [],
|
|
290
|
-
blocked: [],
|
|
291
|
-
}));
|
|
292
|
-
const autoCompleted = autoCompleteResult.completed;
|
|
293
285
|
const messageParts = [
|
|
294
286
|
`Worktrees removed: ${worktreesRemoved.length}`,
|
|
295
287
|
`Branches removed: ${branchesRemoved.length}`,
|
|
296
288
|
`Remote pruned: ${remotePruned ? 'yes' : 'no'}`,
|
|
297
289
|
`Stale stashes (>${STALE_DAYS_THRESHOLD}d): ${staleStashes.length}`,
|
|
298
290
|
...(force ? [`Stashes dropped: ${stashesDropped}`] : ['Use force:true to drop stale stashes']),
|
|
299
|
-
...(autoCompleted.length > 0 ? [`Specs auto-completed: ${autoCompleted.join(', ')}`] : []),
|
|
300
|
-
...(autoCompleteResult.blocked.length > 0
|
|
301
|
-
? [`Specs blocked (gates): ${autoCompleteResult.blocked.map((b) => b.specId).join(', ')}`]
|
|
302
|
-
: []),
|
|
303
291
|
...(errors.length > 0 ? [`Errors: ${errors.length}`] : []),
|
|
304
292
|
];
|
|
305
293
|
const report = {
|
|
@@ -311,7 +299,6 @@ export async function handleCleanup(projectId, config) {
|
|
|
311
299
|
stashesDropped,
|
|
312
300
|
errors,
|
|
313
301
|
message: messageParts.join(' | '),
|
|
314
|
-
...(autoCompleted.length > 0 ? { autoCompleted } : {}),
|
|
315
302
|
};
|
|
316
303
|
const lines = [report.message];
|
|
317
304
|
if (worktreesRemoved.length > 0) {
|
|
@@ -323,9 +310,6 @@ export async function handleCleanup(projectId, config) {
|
|
|
323
310
|
if (staleStashes.length > 0) {
|
|
324
311
|
lines.push('', `**Stale stashes**: ${staleStashes.map((s) => `${s.ref} (${String(s.daysOld)}d)`).join(', ')}`);
|
|
325
312
|
}
|
|
326
|
-
if (autoCompleted.length > 0) {
|
|
327
|
-
lines.push('', `**Auto-completed specs**: ${autoCompleted.join(', ')}`);
|
|
328
|
-
}
|
|
329
313
|
if (errors.length > 0) {
|
|
330
314
|
lines.push('', `**Errors**: ${errors.join('; ')}`);
|
|
331
315
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { git, resolveProjectPath, resolveBaseBranch, mergeConfig } from './git-helpers.js';
|
|
2
2
|
import { specStore } from '../../storage/index.js';
|
|
3
|
-
import { autoCompleteSpecs } from './
|
|
3
|
+
import { autoCompleteSpecs } from './auto-complete-ops.js';
|
|
4
4
|
import { compactResult, formatKeyValue, formatList } from '../output-formatter.js';
|
|
5
5
|
export async function handleRelease(projectId, config) {
|
|
6
6
|
const projectPath = await resolveProjectPath(projectId);
|
|
@@ -13,7 +13,7 @@ import { checkBundledVersionGap } from '../../engine/version-detector/bundled-ve
|
|
|
13
13
|
import { checkAndFixBundledVersion } from '../../engine/mcp-config/mcp-config-writer.js';
|
|
14
14
|
import { buildInitProjectResult } from './result-builder.js';
|
|
15
15
|
import { fetchRecommendedSkills } from '../suggest-tooling/skills-fetcher.js';
|
|
16
|
-
import { autoCompleteSpecs } from '../git/
|
|
16
|
+
import { autoCompleteSpecs } from '../git/auto-complete-ops.js';
|
|
17
17
|
import { regeneratePages } from '../../engine/doc-generator/portal/index.js';
|
|
18
18
|
import { detectStackPatterns } from './stack-detector.js';
|
|
19
19
|
import { buildProjectConfig } from './config-builder.js';
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// tools/manage-hooks.ts — MCP tool handler for manage_hooks (SPEC-071)
|
|
2
2
|
import { hooksStore } from '../storage/index.js';
|
|
3
|
-
import { dispatchEvent, dryRunDispatch
|
|
3
|
+
import { dispatchEvent, dryRunDispatch } from '../engine/hooks/core.js';
|
|
4
|
+
import { getTemplate, listTemplateNames } from '../engine/hooks/templates.js';
|
|
5
|
+
import { generateGitHooks, formatGitHookInstallGuide } from '../engine/hooks/git-hook-generator.js';
|
|
4
6
|
import { compactResult, compactError, formatKeyValue, formatTable, formatList, formatConfirmation, } from './output-formatter.js';
|
|
5
7
|
// ---------------------------------------------------------------------------
|
|
6
8
|
// Operation handlers
|
|
@@ -8,7 +8,7 @@ import { promisify } from 'node:util';
|
|
|
8
8
|
import { join } from 'node:path';
|
|
9
9
|
import { readFile } from 'node:fs/promises';
|
|
10
10
|
import { shouldSuggestBtw, buildBtwHint } from '../engine/context-advisor/btw-advisor.js';
|
|
11
|
-
import { autoCompleteSpecs } from './git/
|
|
11
|
+
import { autoCompleteSpecs } from './git/auto-complete-ops.js';
|
|
12
12
|
import { computeSessionAgeMinutes } from '../engine/session-handoff-generator.js';
|
|
13
13
|
import { loadSessionState } from '../storage/session-state-store.js';
|
|
14
14
|
import { consumeUpdateBanner } from '../engine/update-notifier.js';
|
|
@@ -4,10 +4,13 @@ import { extractCriteria, extractListItems, extractSection, } from '../../engine
|
|
|
4
4
|
import { stripFrontmatter } from '../../engine/frontmatter-parser.js';
|
|
5
5
|
import { readEvidenceArtifacts } from '../../engine/evidence-gates/artifact-reader.js';
|
|
6
6
|
import { checkLifecycleEvidenceGate } from '../../engine/evidence-gates/lifecycle-gate.js';
|
|
7
|
+
import { buildSpecEvidenceIndex } from '../../engine/evidence-index/index-builder.js';
|
|
8
|
+
import { checkDoneDriftContract } from '../../engine/evidence-index/done-drift.js';
|
|
9
|
+
import { parseTechnicalReferenceGroundingRecords } from '../../engine/spec-grounding/contract.js';
|
|
7
10
|
/** SPEC-1054: BDD/SDD lifecycle evidence gate. */
|
|
8
11
|
export async function checkLifecycleEvidenceTransitionGate(args) {
|
|
9
|
-
const [
|
|
10
|
-
|
|
12
|
+
const [criteriaResult, artifacts] = await Promise.all([
|
|
13
|
+
extractLifecycleGateCriteriaAndBody(args.spec, args.projectPath),
|
|
11
14
|
readEvidenceArtifacts({
|
|
12
15
|
spec: args.spec,
|
|
13
16
|
projectId: args.projectId,
|
|
@@ -15,13 +18,26 @@ export async function checkLifecycleEvidenceTransitionGate(args) {
|
|
|
15
18
|
projectPath: args.projectPath,
|
|
16
19
|
}),
|
|
17
20
|
]);
|
|
21
|
+
const { criteria, body } = criteriaResult;
|
|
18
22
|
const result = checkLifecycleEvidenceGate({
|
|
19
23
|
transition: args.transition,
|
|
20
24
|
spec: args.spec,
|
|
21
25
|
criteria,
|
|
22
26
|
artifacts,
|
|
23
27
|
});
|
|
24
|
-
if (
|
|
28
|
+
if (args.transition === 'done') {
|
|
29
|
+
const index = await buildSpecEvidenceIndex({
|
|
30
|
+
specId: args.specId,
|
|
31
|
+
criteria,
|
|
32
|
+
artifacts,
|
|
33
|
+
projectPath: args.projectPath,
|
|
34
|
+
});
|
|
35
|
+
const groundedTechnicalPaths = body
|
|
36
|
+
? parseTechnicalReferenceGroundingRecords(body).map((record) => record.path)
|
|
37
|
+
: [];
|
|
38
|
+
result.issues.push(...checkDoneDriftContract({ index, groundedTechnicalPaths }));
|
|
39
|
+
}
|
|
40
|
+
if (result.issues.length === 0) {
|
|
25
41
|
return null;
|
|
26
42
|
}
|
|
27
43
|
return evidenceGateError({
|
|
@@ -34,7 +50,7 @@ export async function checkLifecycleEvidenceTransitionGate(args) {
|
|
|
34
50
|
requiredContractKinds: result.requiredContractKinds,
|
|
35
51
|
});
|
|
36
52
|
}
|
|
37
|
-
async function
|
|
53
|
+
async function extractLifecycleGateCriteriaAndBody(spec, projectPath) {
|
|
38
54
|
const specPath = isAbsolute(spec.specPath) || !projectPath ? spec.specPath : join(projectPath, spec.specPath);
|
|
39
55
|
try {
|
|
40
56
|
const raw = await readFile(specPath, 'utf-8');
|
|
@@ -43,14 +59,14 @@ async function extractLifecycleGateCriteria(spec, projectPath) {
|
|
|
43
59
|
if (acceptanceCriteria) {
|
|
44
60
|
const items = extractListItems(acceptanceCriteria);
|
|
45
61
|
if (items.length > 0) {
|
|
46
|
-
return [...new Set(items)];
|
|
62
|
+
return { criteria: [...new Set(items)], body: raw };
|
|
47
63
|
}
|
|
48
64
|
}
|
|
49
65
|
}
|
|
50
66
|
catch {
|
|
51
67
|
// Fall back to the broader validator extractor when the canonical file is unavailable.
|
|
52
68
|
}
|
|
53
|
-
return extractCriteria(spec);
|
|
69
|
+
return { criteria: await extractCriteria(spec), body: null };
|
|
54
70
|
}
|
|
55
71
|
function evidenceGateError(args) {
|
|
56
72
|
return {
|
|
@@ -72,6 +88,7 @@ function evidenceGateError(args) {
|
|
|
72
88
|
structuredContent: {
|
|
73
89
|
error: 'lifecycle_evidence_gate_failed',
|
|
74
90
|
code: 422,
|
|
91
|
+
sddConstitutionRuleId: args.transition === 'done' ? 'sdd.done-evidence-required' : 'sdd.grounded-contract',
|
|
75
92
|
context: {
|
|
76
93
|
specId: args.specId,
|
|
77
94
|
transition: args.transition,
|
|
@@ -6,6 +6,8 @@ import { dispatchFeedbackEvent } from '../learn.js';
|
|
|
6
6
|
import { scoreAmbiguityFromPath } from '../../engine/ambiguity-scorer.js';
|
|
7
7
|
import { checkReadinessInternal } from '../../engine/readiness-checker.js';
|
|
8
8
|
import { validateEnglishOnlySpecText } from '../../engine/spec-language/english-only.js';
|
|
9
|
+
import { checkGroundedSpecContract } from '../../engine/spec-grounding/contract.js';
|
|
10
|
+
import { checkGenericSpecOutput } from '../../engine/spec-quality/generic-output-gate.js';
|
|
9
11
|
/**
|
|
10
12
|
* Valid state transitions for spec lifecycle.
|
|
11
13
|
* draft -> review -> approved -> implementing -> done
|
|
@@ -224,6 +226,53 @@ export async function checkReadinessGate(spec, newStatus, forceApprove) {
|
|
|
224
226
|
qualityWarnings: [],
|
|
225
227
|
};
|
|
226
228
|
}
|
|
229
|
+
const groundingResult = checkGroundedSpecContract(spec, body);
|
|
230
|
+
if (!groundingResult.passed) {
|
|
231
|
+
return {
|
|
232
|
+
blockResult: {
|
|
233
|
+
content: [
|
|
234
|
+
{
|
|
235
|
+
type: 'text',
|
|
236
|
+
text: `Grounded spec contract blocked transition to ${newStatus}. ` +
|
|
237
|
+
groundingResult.issues.slice(0, 3).join('; '),
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
isError: true,
|
|
241
|
+
structuredContent: {
|
|
242
|
+
error: 'GROUNDED_SPEC_CONTRACT_BLOCKED',
|
|
243
|
+
sddConstitutionRuleId: 'sdd.grounded-contract',
|
|
244
|
+
issues: groundingResult.issues,
|
|
245
|
+
fixHint: 'Add grounding metadata for every contract criterion or move ungrounded criteria to advisory/discovery before review or approval.',
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
qualityWarnings: [],
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
const genericOutputGate = checkGenericSpecOutput(body);
|
|
252
|
+
if (!genericOutputGate.passed) {
|
|
253
|
+
return {
|
|
254
|
+
blockResult: {
|
|
255
|
+
content: [
|
|
256
|
+
{
|
|
257
|
+
type: 'text',
|
|
258
|
+
text: `Spec quality gate blocked transition to ${newStatus}. ` +
|
|
259
|
+
genericOutputGate.issues
|
|
260
|
+
.slice(0, 3)
|
|
261
|
+
.map((issue) => `${issue.phrase}: ${issue.reason}`)
|
|
262
|
+
.join('; '),
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
isError: true,
|
|
266
|
+
structuredContent: {
|
|
267
|
+
error: 'GENERIC_SPEC_OUTPUT_BLOCKED',
|
|
268
|
+
sddConstitutionRuleId: 'sdd.no-generic-output',
|
|
269
|
+
issues: genericOutputGate.issues,
|
|
270
|
+
fixHint: 'Replace generic criteria or placeholder references with grounded, testable behavior before review or approval.',
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
qualityWarnings: [],
|
|
274
|
+
};
|
|
275
|
+
}
|
|
227
276
|
let score;
|
|
228
277
|
let warnings;
|
|
229
278
|
let criteriaCount;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { specStore, knowledgeStore } from '../storage/index.js';
|
|
2
|
-
import { handleCreateBranch } from './git/branch-ops.js';
|
|
3
2
|
import { checkConstitutionCompliance } from './create-spec-tech.js';
|
|
4
|
-
import { cleanupSpecOnDone } from './git/cleanup-ops.js';
|
|
5
3
|
import { withAudit } from '../engine/autopilot/audit-logger.js';
|
|
6
4
|
import { hasPending, markPending, clearPending } from '../engine/autopilot/cascade-deduplicator.js';
|
|
7
5
|
export async function runImplementingActions(projectId, specId) {
|
|
@@ -27,10 +25,12 @@ export async function runImplementingActions(projectId, specId) {
|
|
|
27
25
|
let branchResult;
|
|
28
26
|
try {
|
|
29
27
|
const { resolveProjectPath: resolveBranchPath } = await import('./git/git-helpers.js');
|
|
28
|
+
const { handleCreateBranch } = await import('./git/branch-ops.js');
|
|
30
29
|
const branchProjectPath = await resolveBranchPath(projectId);
|
|
31
30
|
branchResult = await withAudit(branchProjectPath, 'update_status(implementing)', 'handleCreateBranch', () => handleCreateBranch(projectId, specId), (r) => ({ isError: r.isError }));
|
|
32
31
|
}
|
|
33
32
|
catch {
|
|
33
|
+
const { handleCreateBranch } = await import('./git/branch-ops.js');
|
|
34
34
|
branchResult = await handleCreateBranch(projectId, specId);
|
|
35
35
|
}
|
|
36
36
|
if (!branchResult.isError) {
|
|
@@ -224,9 +224,12 @@ export async function runDoneActions(projectId, specId, gitBranch) {
|
|
|
224
224
|
// SPEC-491: Sweep planu/ to remove legacy files
|
|
225
225
|
// SPEC-496: Auto-generate portable session context snapshot
|
|
226
226
|
void Promise.allSettled([
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
227
|
+
(async () => {
|
|
228
|
+
const { cleanupSpecOnDone } = await import('./git/cleanup-ops.js');
|
|
229
|
+
await withAudit(projectPath, 'update_status(done)', 'cleanupSpecOnDone', () => cleanupSpecOnDone(projectPath, specId, gitBranch)).catch(() => {
|
|
230
|
+
/* best-effort — never blocks status transition */
|
|
231
|
+
});
|
|
232
|
+
})(),
|
|
230
233
|
// SPEC-633: Remove spec from session.json index when done
|
|
231
234
|
(async () => {
|
|
232
235
|
const { removeSpecFromSession } = await import('../engine/session-state/writer.js');
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type ActionableSpecMetricName = 'groundingCoverage' | 'verificationCoverage' | 'ambiguityCount' | 'driftRisk';
|
|
2
|
+
export interface ActionableSpecMetric {
|
|
3
|
+
name: ActionableSpecMetricName;
|
|
4
|
+
value: number | 'none' | 'low' | 'medium' | 'high';
|
|
5
|
+
inputs: string[];
|
|
6
|
+
nextAction: string;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=actionable-spec-metrics.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { InteractiveQuestion } from './
|
|
1
|
+
import type { InteractiveQuestion } from './interactive-question.js';
|
|
2
2
|
import type { ToolResult } from './common/primitives.js';
|
|
3
3
|
/**
|
|
4
4
|
* Persisted token that blocks a tool from proceeding until the LLM relays
|
|
@@ -1,21 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
/** Display text — add "(Recommended)" suffix to preferred option. */
|
|
4
|
-
label: string;
|
|
5
|
-
/** What this option means or what happens if chosen. */
|
|
6
|
-
description: string;
|
|
7
|
-
}
|
|
8
|
-
/** A structured question for the user, presented via the LLM's native UI. */
|
|
9
|
-
export interface InteractiveQuestion {
|
|
10
|
-
/** The full question text, ending with "?". */
|
|
11
|
-
question: string;
|
|
12
|
-
/** Short chip/tag label (max 12 chars). E.g. "Auth", "Scope", "Target". */
|
|
13
|
-
header: string;
|
|
14
|
-
/** 2-4 options to choose from. An "Other" free-text option is added automatically by the LLM. */
|
|
15
|
-
options: InteractiveOption[];
|
|
16
|
-
/** True for non-exclusive choices (user can pick multiple). */
|
|
17
|
-
multiSelect: boolean;
|
|
18
|
-
}
|
|
1
|
+
import type { InteractiveQuestion } from './interactive-question.js';
|
|
2
|
+
export type { InteractiveOption, InteractiveQuestion } from './interactive-question.js';
|
|
19
3
|
/** Tool response when interactive clarification is needed instead of creating a spec. */
|
|
20
4
|
export interface InteractiveClarificationResponse {
|
|
21
5
|
needsClarification: true;
|
|
@@ -54,7 +54,7 @@ export interface EvidenceArtifacts {
|
|
|
54
54
|
invalidArtifacts: string[];
|
|
55
55
|
}
|
|
56
56
|
export interface EvidenceGateIssue {
|
|
57
|
-
code: 'discovery_missing' | 'discovery_unresolved_questions' | 'task_plan_missing' | 'task_plan_uncovered_criteria' | 'traceability_missing' | 'traceability_uncovered_criteria' | 'traceability_incomplete_rows' | 'contract_validation_missing' | 'contract_validation_failed' | 'evidence_artifact_invalid';
|
|
57
|
+
code: 'discovery_missing' | 'discovery_unresolved_questions' | 'task_plan_missing' | 'task_plan_uncovered_criteria' | 'traceability_missing' | 'traceability_uncovered_criteria' | 'traceability_incomplete_rows' | 'contract_validation_missing' | 'contract_validation_failed' | 'done_drift_uncovered_criteria' | 'done_drift_stale_evidence' | 'done_drift_unapproved_scope' | 'evidence_artifact_invalid';
|
|
58
58
|
message: string;
|
|
59
59
|
}
|
|
60
60
|
export interface EvidenceGateResult {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type EvidenceRecordKind = 'scenario' | 'test' | 'contract' | 'manual' | 'changed-file' | 'validation' | 'reviewer';
|
|
2
|
+
export type EvidenceRecordStatus = 'valid' | 'stale' | 'invalid' | 'missing';
|
|
3
|
+
export interface EvidenceRecord {
|
|
4
|
+
kind: EvidenceRecordKind;
|
|
5
|
+
value: string;
|
|
6
|
+
status: EvidenceRecordStatus;
|
|
7
|
+
source: string;
|
|
8
|
+
command?: string;
|
|
9
|
+
passed?: boolean;
|
|
10
|
+
timestamp?: string;
|
|
11
|
+
reason?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface CriterionEvidenceIndexEntry {
|
|
14
|
+
criterion: string;
|
|
15
|
+
criterionKey: string;
|
|
16
|
+
evidence: EvidenceRecord[];
|
|
17
|
+
}
|
|
18
|
+
export interface SpecEvidenceIndex {
|
|
19
|
+
specId: string;
|
|
20
|
+
generatedAt: string;
|
|
21
|
+
criteria: CriterionEvidenceIndexEntry[];
|
|
22
|
+
diagnostics: string[];
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=evidence-index.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ToolResult } from './common/primitives.js';
|
|
2
|
+
import type { UpdateStatusInput } from './spec/inputs.js';
|
|
3
|
+
export interface HookUpdateStatusInput extends UpdateStatusInput {
|
|
4
|
+
trigger: 'fs-hook';
|
|
5
|
+
actor: 'system';
|
|
6
|
+
}
|
|
7
|
+
export interface UpdateStatusModule {
|
|
8
|
+
handleUpdateStatus: (input: HookUpdateStatusInput) => Promise<ToolResult>;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=hook-status-update.d.ts.map
|
package/dist/types/index.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export * from './migration/index.js';
|
|
|
25
25
|
export * from './migration-advanced.js';
|
|
26
26
|
export * from './privacy.js';
|
|
27
27
|
export * from './events.js';
|
|
28
|
+
export * from './evidence-gates.js';
|
|
29
|
+
export * from './evidence-index.js';
|
|
28
30
|
export * from './analytics.js';
|
|
29
31
|
export * from './sdd-flow.js';
|
|
30
32
|
export * from './sdd-model-routing.js';
|
|
@@ -48,6 +50,7 @@ export * from './dynamic-migration.js';
|
|
|
48
50
|
export * from './multi-agent.js';
|
|
49
51
|
export * from './ai-native.js';
|
|
50
52
|
export * from './actuals-tracking.js';
|
|
53
|
+
export * from './actionable-spec-metrics.js';
|
|
51
54
|
export * from './actuals.js';
|
|
52
55
|
export * from './risk.js';
|
|
53
56
|
export * from './context-budget.js';
|
|
@@ -57,10 +60,12 @@ export * from './changelog.js';
|
|
|
57
60
|
export * from './coverage.js';
|
|
58
61
|
export * from './ai-cost.js';
|
|
59
62
|
export * from './hooks.js';
|
|
63
|
+
export * from './hook-status-update.js';
|
|
60
64
|
export * from './webhook.js';
|
|
61
65
|
export * from './ci.js';
|
|
62
66
|
export * from './spec-templates.js';
|
|
63
67
|
export * from './spec-generator.js';
|
|
68
|
+
export * from './spec-grounding.js';
|
|
64
69
|
export * from './registry.js';
|
|
65
70
|
export * from './tool-groups.js';
|
|
66
71
|
export * from './code-transforms.js';
|
|
@@ -103,6 +108,7 @@ export * from './legal.js';
|
|
|
103
108
|
export * from './mcp-recommendation.js';
|
|
104
109
|
export * from './approval.js';
|
|
105
110
|
export * from './scan-project.js';
|
|
111
|
+
export * from './sdd-constitution.js';
|
|
106
112
|
export * from './import-spec.js';
|
|
107
113
|
export * from './error-telemetry.js';
|
|
108
114
|
export * from './audit-trail.js';
|
package/dist/types/index.js
CHANGED
|
@@ -26,6 +26,8 @@ export * from './migration/index.js';
|
|
|
26
26
|
export * from './migration-advanced.js';
|
|
27
27
|
export * from './privacy.js';
|
|
28
28
|
export * from './events.js';
|
|
29
|
+
export * from './evidence-gates.js';
|
|
30
|
+
export * from './evidence-index.js';
|
|
29
31
|
export * from './analytics.js';
|
|
30
32
|
export * from './sdd-flow.js';
|
|
31
33
|
export * from './sdd-model-routing.js';
|
|
@@ -49,6 +51,7 @@ export * from './dynamic-migration.js';
|
|
|
49
51
|
export * from './multi-agent.js';
|
|
50
52
|
export * from './ai-native.js';
|
|
51
53
|
export * from './actuals-tracking.js';
|
|
54
|
+
export * from './actionable-spec-metrics.js';
|
|
52
55
|
export * from './actuals.js';
|
|
53
56
|
export * from './risk.js';
|
|
54
57
|
export * from './context-budget.js';
|
|
@@ -58,10 +61,12 @@ export * from './changelog.js';
|
|
|
58
61
|
export * from './coverage.js';
|
|
59
62
|
export * from './ai-cost.js';
|
|
60
63
|
export * from './hooks.js';
|
|
64
|
+
export * from './hook-status-update.js';
|
|
61
65
|
export * from './webhook.js';
|
|
62
66
|
export * from './ci.js';
|
|
63
67
|
export * from './spec-templates.js';
|
|
64
68
|
export * from './spec-generator.js';
|
|
69
|
+
export * from './spec-grounding.js';
|
|
65
70
|
export * from './registry.js';
|
|
66
71
|
export * from './tool-groups.js';
|
|
67
72
|
export * from './code-transforms.js';
|
|
@@ -104,6 +109,7 @@ export * from './legal.js';
|
|
|
104
109
|
export * from './mcp-recommendation.js';
|
|
105
110
|
export * from './approval.js';
|
|
106
111
|
export * from './scan-project.js';
|
|
112
|
+
export * from './sdd-constitution.js';
|
|
107
113
|
export * from './import-spec.js';
|
|
108
114
|
export * from './error-telemetry.js';
|
|
109
115
|
export * from './audit-trail.js';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** A single option in an interactive question. */
|
|
2
|
+
export interface InteractiveOption {
|
|
3
|
+
/** Display text — add "(Recommended)" suffix to preferred option. */
|
|
4
|
+
label: string;
|
|
5
|
+
/** What this option means or what happens if chosen. */
|
|
6
|
+
description: string;
|
|
7
|
+
}
|
|
8
|
+
/** A structured question for the user, presented via the LLM's native UI. */
|
|
9
|
+
export interface InteractiveQuestion {
|
|
10
|
+
/** The full question text, ending with "?". */
|
|
11
|
+
question: string;
|
|
12
|
+
/** Short chip/tag label (max 12 chars). E.g. "Auth", "Scope", "Target". */
|
|
13
|
+
header: string;
|
|
14
|
+
/** 2-4 options to choose from. An "Other" free-text option is added automatically by the LLM. */
|
|
15
|
+
options: InteractiveOption[];
|
|
16
|
+
/** True for non-exclusive choices (user can pick multiple). */
|
|
17
|
+
multiSelect: boolean;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=interactive-question.d.ts.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type SddConstitutionRuleLevel = 'blocking' | 'advisory';
|
|
2
|
+
export interface SddConstitutionRule {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
level: SddConstitutionRuleLevel;
|
|
6
|
+
category: 'grounding' | 'quality' | 'evidence' | 'approval' | 'metrics';
|
|
7
|
+
description: string;
|
|
8
|
+
nextAction: string;
|
|
9
|
+
}
|
|
10
|
+
export interface SddConstitutionOverride {
|
|
11
|
+
ruleId: string;
|
|
12
|
+
level?: SddConstitutionRuleLevel;
|
|
13
|
+
enabled?: boolean;
|
|
14
|
+
rationale: string;
|
|
15
|
+
}
|
|
16
|
+
export interface EffectiveSddConstitutionRule extends SddConstitutionRule {
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
overrideRationale?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface SddConstitutionViolation {
|
|
21
|
+
ruleId: string;
|
|
22
|
+
level: SddConstitutionRuleLevel;
|
|
23
|
+
message: string;
|
|
24
|
+
evidence: string[];
|
|
25
|
+
nextAction: string;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=sdd-constitution.d.ts.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Spec } from './spec/core.js';
|
|
2
2
|
import type { Estimation } from './estimation.js';
|
|
3
3
|
import type { SpecStatus } from './common/index.js';
|
|
4
|
+
import type { CriterionGroundingRecord, TechnicalReferenceGroundingRecord } from './spec-grounding.js';
|
|
4
5
|
/** A criterion in the lean spec frontmatter. */
|
|
5
6
|
export interface LeanCriterion {
|
|
6
7
|
text: string;
|
|
@@ -29,6 +30,12 @@ export interface LeanSpecInput {
|
|
|
29
30
|
estimation: Estimation;
|
|
30
31
|
/** SPEC-461 Phase 3: Extra criteria from autopilot pattern detection. */
|
|
31
32
|
extraCriteria?: string[];
|
|
33
|
+
/** SPEC-1074: Explicit criteria after grounding filters are applied. */
|
|
34
|
+
criteriaOverride?: LeanCriterion[];
|
|
35
|
+
/** SPEC-1074: Criterion-level grounding records for new specs. */
|
|
36
|
+
groundingCriteria?: CriterionGroundingRecord[];
|
|
37
|
+
/** SPEC-1074: Technical reference grounding records for new specs. */
|
|
38
|
+
groundingTechnicalReferences?: TechnicalReferenceGroundingRecord[];
|
|
32
39
|
/** SPEC-481: Acceptance criteria format. Defaults to 'checkbox'. */
|
|
33
40
|
acFormat?: 'checkbox' | 'bdd';
|
|
34
41
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type GroundingSource = 'user_input' | 'project_evidence' | 'documented_assumption' | 'ungrounded_advisory';
|
|
2
|
+
export type GroundingConfidence = 'low' | 'medium' | 'high';
|
|
3
|
+
export interface CriterionGroundingRecord {
|
|
4
|
+
text: string;
|
|
5
|
+
source: GroundingSource;
|
|
6
|
+
evidence: string[];
|
|
7
|
+
confidence: GroundingConfidence;
|
|
8
|
+
}
|
|
9
|
+
export interface TechnicalReferenceGroundingRecord {
|
|
10
|
+
path: string;
|
|
11
|
+
section: 'create' | 'modify' | 'test';
|
|
12
|
+
source: GroundingSource;
|
|
13
|
+
evidence: string[];
|
|
14
|
+
confidence: GroundingConfidence;
|
|
15
|
+
}
|
|
16
|
+
export interface GroundingGateResult {
|
|
17
|
+
passed: boolean;
|
|
18
|
+
required: boolean;
|
|
19
|
+
issues: string[];
|
|
20
|
+
records: CriterionGroundingRecord[];
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=spec-grounding.d.ts.map
|
|
@@ -35,6 +35,16 @@ export interface SpecQualityReport {
|
|
|
35
35
|
risk: QualityDimensionDetail;
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
|
+
export type GenericSpecOutputIssueKind = 'generic-criterion' | 'unsupported-verification' | 'placeholder-reference' | 'generic-technical-reference';
|
|
39
|
+
export interface GenericSpecOutputIssue {
|
|
40
|
+
kind: GenericSpecOutputIssueKind;
|
|
41
|
+
phrase: string;
|
|
42
|
+
reason: string;
|
|
43
|
+
}
|
|
44
|
+
export interface GenericSpecOutputGateResult {
|
|
45
|
+
passed: boolean;
|
|
46
|
+
issues: GenericSpecOutputIssue[];
|
|
47
|
+
}
|
|
38
48
|
/** A single over-engineering signal detected in a spec. SPEC-485 */
|
|
39
49
|
export interface ComplexitySignal {
|
|
40
50
|
type: string;
|
package/dist/types/storage.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* A minimal CRUD store backed by a single JSON array file.
|
|
3
3
|
* Items must have an `id: string` field for lookup / update / remove.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* JSON-backed stores expose this shape when they implement CRUD operations.
|
|
6
6
|
*/
|
|
7
7
|
export interface CrudStore<T extends {
|
|
8
8
|
id: string;
|