@planu/cli 4.11.1 → 4.11.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.
Files changed (155) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/config/elicitation-questions.json +5 -17
  3. package/dist/engine/cascade-hooks/registry.js +0 -2
  4. package/dist/engine/challenge-scenarios-loader.d.ts +3 -3
  5. package/dist/engine/compliance-injector.d.ts +3 -3
  6. package/dist/engine/dashboard/kanban-logic.d.ts +1 -1
  7. package/dist/engine/doc-generator/portal/risk-matrix-svg.d.ts +1 -1
  8. package/dist/engine/doc-generator/proposal/proposal-generator.d.ts +1 -1
  9. package/dist/engine/elicitation/answer-extractor.d.ts +1 -4
  10. package/dist/engine/elicitation/answer-extractor.js +197 -68
  11. package/dist/engine/elicitation/decision-gap-detector.js +59 -79
  12. package/dist/engine/elicitation/index.d.ts +0 -1
  13. package/dist/engine/elicitation/index.js +0 -1
  14. package/dist/engine/elicitation/question-generator.js +10 -3
  15. package/dist/engine/elicitation/question-grounding-gate.js +83 -34
  16. package/dist/engine/evidence-gates/lifecycle-gate.js +27 -14
  17. package/dist/engine/evidence-index/index-builder.js +7 -2
  18. package/dist/engine/frontmatter-sha/frozen-fields.d.ts +1 -1
  19. package/dist/engine/handoff-artifacts/schemas/red-tests-handoff.d.ts +2 -2
  20. package/dist/engine/handoff-artifacts/schemas.d.ts +22 -22
  21. package/dist/engine/human-summary.js +3 -3
  22. package/dist/engine/implementation-contract/common.d.ts +1 -1
  23. package/dist/engine/implementation-contract/renderer.js +17 -39
  24. package/dist/engine/keyword-matcher/stopwords.d.ts +2 -2
  25. package/dist/engine/lifecycle-hints.d.ts +1 -1
  26. package/dist/engine/lifecycle-hints.js +13 -29
  27. package/dist/engine/provider-adapters/adapters/claude.d.ts +1 -1
  28. package/dist/engine/provider-adapters/adapters/gemini.d.ts +1 -1
  29. package/dist/engine/provider-adapters/adapters/gpt4.d.ts +1 -1
  30. package/dist/engine/provider-adapters/adapters/markdown.d.ts +1 -1
  31. package/dist/engine/safety/atomic-write-file.d.ts +1 -1
  32. package/dist/engine/safety/cross-process-lock.d.ts +1 -1
  33. package/dist/engine/safety/path-sanitizer.d.ts +1 -1
  34. package/dist/engine/security-analyzer/profile-detector.js +48 -16
  35. package/dist/engine/spec-format/copula-blacklist.d.ts +2 -2
  36. package/dist/engine/spec-format/lean-spec-generator.d.ts +2 -2
  37. package/dist/engine/spec-format/lean-spec-generator.js +9 -10
  38. package/dist/engine/spec-format/metadata-value-policy.d.ts +20 -20
  39. package/dist/engine/spec-generator/fallback-generator.js +13 -20
  40. package/dist/engine/spec-grounding/contract.d.ts +5 -0
  41. package/dist/engine/spec-grounding/contract.js +10 -6
  42. package/dist/engine/spec-migrator/lean-migration.js +9 -16
  43. package/dist/engine/text-signal-boundaries.js +45 -12
  44. package/dist/engine/universal-rules/rules/planu-modes.js +0 -2
  45. package/dist/engine/web-fetcher/stack-advisor.d.ts +2 -2
  46. package/dist/engine/web-fetcher/stack-advisor.js +78 -17
  47. package/dist/engine/well-known/manifest-schema.d.ts +33 -33
  48. package/dist/engine/workers/schema.d.ts +3 -3
  49. package/dist/hosts/claude-code/ux/mcp-resources.d.ts +4 -4
  50. package/dist/hosts/codex/rest-shim.d.ts +1 -1
  51. package/dist/native/lightweight-command-catalog.d.ts +30 -30
  52. package/dist/server/openapi-spec.d.ts +132 -132
  53. package/dist/tools/challenge-spec/challenge-report.d.ts +13 -0
  54. package/dist/tools/challenge-spec/challenge-report.js +55 -0
  55. package/dist/tools/challenge-spec/event-challenge-scenarios.js +8 -27
  56. package/dist/tools/challenge-spec/resilience-challenge-scenarios-a.js +16 -32
  57. package/dist/tools/challenge-spec/resilience-challenge-scenarios-b.js +15 -20
  58. package/dist/tools/challenge-spec/scenario-collector.d.ts +13 -0
  59. package/dist/tools/challenge-spec/scenario-collector.js +86 -0
  60. package/dist/tools/challenge-spec/scenarios-data.js +10 -9
  61. package/dist/tools/challenge-spec/scenarios-failure.js +38 -54
  62. package/dist/tools/challenge-spec/scenarios-scale.js +14 -15
  63. package/dist/tools/challenge-spec/scenarios-security.d.ts +1 -1
  64. package/dist/tools/challenge-spec/scenarios-security.js +18 -11
  65. package/dist/tools/challenge-spec/scenarios-utils.d.ts +31 -5
  66. package/dist/tools/challenge-spec/scenarios-utils.js +102 -6
  67. package/dist/tools/challenge-spec/security-challenge-scenarios.d.ts +1 -1
  68. package/dist/tools/challenge-spec/security-challenge-scenarios.js +20 -25
  69. package/dist/tools/challenge-spec.js +46 -124
  70. package/dist/tools/clarify-requirements/multiple-choice.js +1 -1
  71. package/dist/tools/clarify-requirements/questions-context.js +11 -11
  72. package/dist/tools/clarify-requirements/questions.js +3 -23
  73. package/dist/tools/clarify-requirements.js +38 -0
  74. package/dist/tools/compliance-gate-handler.d.ts +1 -1
  75. package/dist/tools/create-spec/autopilot-analyzer.d.ts +1 -1
  76. package/dist/tools/create-spec/autopilot-analyzer.js +29 -54
  77. package/dist/tools/create-spec/post-creation.js +23 -4
  78. package/dist/tools/create-spec.js +51 -168
  79. package/dist/tools/delete-first/suggest-deletions.d.ts +1 -1
  80. package/dist/tools/elicit-requirements-handler.js +20 -12
  81. package/dist/tools/generate-batch-script.d.ts +2 -2
  82. package/dist/tools/init-project/rules-generator.js +0 -1
  83. package/dist/tools/oauth-handler.d.ts +1 -1
  84. package/dist/tools/register-sdd-tools.d.ts +1 -1
  85. package/dist/tools/register-spec-tools/core-spec-tools.js +11 -2
  86. package/dist/tools/render-spec-for-provider.d.ts +2 -2
  87. package/dist/tools/scan-orphan-spec-refs.d.ts +6 -6
  88. package/dist/tools/schemas/agents.d.ts +7 -7
  89. package/dist/tools/schemas/analysis.d.ts +11 -11
  90. package/dist/tools/schemas/delete.d.ts +5 -5
  91. package/dist/tools/schemas/github.d.ts +4 -4
  92. package/dist/tools/schemas/ide-config.d.ts +12 -12
  93. package/dist/tools/schemas/infra.d.ts +9 -9
  94. package/dist/tools/schemas/lifecycle.d.ts +50 -50
  95. package/dist/tools/schemas/llm-provider-schemas.d.ts +7 -7
  96. package/dist/tools/schemas/plugins-schemas.d.ts +12 -12
  97. package/dist/tools/schemas/project-graph.d.ts +6 -6
  98. package/dist/tools/schemas/rbac.d.ts +2 -2
  99. package/dist/tools/schemas/registry.d.ts +3 -3
  100. package/dist/tools/schemas/runtime-security.d.ts +2 -2
  101. package/dist/tools/schemas/session.d.ts +2 -2
  102. package/dist/tools/schemas/spec.d.ts +15 -15
  103. package/dist/tools/schemas/token-intelligence.d.ts +6 -6
  104. package/dist/tools/schemas/token-optimization.d.ts +2 -2
  105. package/dist/tools/schemas/workers-schema.d.ts +4 -4
  106. package/dist/tools/skill-bootstrap-handler.d.ts +7 -7
  107. package/dist/tools/suggest-stack.js +49 -13
  108. package/dist/tools/tool-registry/deprecated-stubs.d.ts +1 -1
  109. package/dist/tools/triage-request.d.ts +3 -3
  110. package/dist/tools/update-status/dod-gates.d.ts +8 -0
  111. package/dist/tools/update-status/dod-gates.js +68 -43
  112. package/dist/tools/update-status/file-sync.d.ts +1 -1
  113. package/dist/tools/update-status/file-sync.js +10 -45
  114. package/dist/tools/update-status/index.js +133 -97
  115. package/dist/tools/update-status/qa-gate.js +26 -0
  116. package/dist/tools/update-status/response-builder.d.ts +1 -1
  117. package/dist/tools/update-status/response-builder.js +11 -19
  118. package/dist/tools/update-status/transition-guard.d.ts +2 -2
  119. package/dist/tools/update-status/transition-guard.js +11 -8
  120. package/dist/tools/update-status-actions.d.ts +0 -1
  121. package/dist/tools/update-status-actions.js +10 -26
  122. package/dist/tools/update-status-convention-gate.d.ts +1 -6
  123. package/dist/tools/update-status-convention-gate.js +9 -33
  124. package/dist/tools/validate-lint.d.ts +1 -1
  125. package/dist/tools/validate-lint.js +39 -1
  126. package/dist/types/docs.d.ts +0 -4
  127. package/dist/types/env.d.ts +0 -4
  128. package/dist/types/estimation.d.ts +0 -2
  129. package/dist/types/multi-teammate-review.d.ts +4 -4
  130. package/dist/types/plugin-install.d.ts +7 -7
  131. package/dist/types/spec/core.d.ts +20 -0
  132. package/dist/types/spec/inputs.d.ts +1 -1
  133. package/dist/types/stack/index.d.ts +1 -1
  134. package/dist/types/stack/recommend.d.ts +10 -0
  135. package/package.json +21 -18
  136. package/planu-native.json +1 -1
  137. package/planu-plugin.json +1 -1
  138. package/src/i18n/messages/en.json +0 -72
  139. package/src/i18n/messages/es.json +0 -72
  140. package/src/i18n/messages/pt.json +0 -72
  141. package/dist/config/elicitation-dimensions.json +0 -51
  142. package/dist/engine/actuals-estimator.d.ts +0 -14
  143. package/dist/engine/actuals-estimator.js +0 -64
  144. package/dist/engine/cascade-hooks/hooks/living-specs.hook.d.ts +0 -3
  145. package/dist/engine/cascade-hooks/hooks/living-specs.hook.js +0 -19
  146. package/dist/engine/elicitation/non-interactive-defaults.d.ts +0 -9
  147. package/dist/engine/elicitation/non-interactive-defaults.js +0 -28
  148. package/dist/engine/elicitation/option-builder.d.ts +0 -9
  149. package/dist/engine/elicitation/option-builder.js +0 -85
  150. package/dist/engine/progress-writer.d.ts +0 -10
  151. package/dist/engine/progress-writer.js +0 -63
  152. package/dist/tools/clarify-requirements/interview-mode.d.ts +0 -42
  153. package/dist/tools/clarify-requirements/interview-mode.js +0 -342
  154. package/dist/tools/update-status/mode-hints.d.ts +0 -4
  155. package/dist/tools/update-status/mode-hints.js +0 -24
@@ -1,5 +1,5 @@
1
1
  // engine/human-summary.ts — Plain-language one/two-sentence summaries for tool responses.
2
- // Rules: no IDs/hashes, no jargon, max 2 sentences, always end with a next-step suggestion.
2
+ // Rules: no IDs/hashes, no jargon, max 2 sentences, and no invented next step for terminal states.
3
3
  /** create_spec — plan was created successfully */
4
4
  export function buildCreateSpecSummary(title, devHours) {
5
5
  const hours = devHours > 0 ? `Estimated ${String(devHours)}h` : 'Estimation ready';
@@ -8,7 +8,7 @@ export function buildCreateSpecSummary(title, devHours) {
8
8
  /** update_status — status transition completed */
9
9
  export function buildUpdateStatusSummary(title, newStatus) {
10
10
  if (newStatus === 'done') {
11
- return `Done! "${title}" is complete and recorded. Run estimate or start a new plan when you're ready.`;
11
+ return `Done! "${title}" is complete and recorded.`;
12
12
  }
13
13
  if (newStatus === 'approved') {
14
14
  return `"${title}" is approved and ready to build. Start implementing whenever you like.`;
@@ -20,7 +20,7 @@ export function buildUpdateStatusSummary(title, newStatus) {
20
20
  return `"${title}" is now under review. Approve it or request changes to move forward.`;
21
21
  }
22
22
  if (newStatus === 'discarded') {
23
- return `"${title}" has been discarded. Create a new plan whenever you want to restart.`;
23
+ return `"${title}" has been discarded.`;
24
24
  }
25
25
  return `"${title}" moved to ${newStatus}. Check the next step suggestion above to continue.`;
26
26
  }
@@ -1,5 +1,5 @@
1
1
  export declare const IMPLEMENTATION_CONTRACT_SECTION = "Implementation Contract";
2
- export declare const IMPLEMENTATION_CONTRACT_SUBSECTIONS: readonly ["User Outcome", "Behavior Contract", "File-Level Work Plan", "Acceptance-To-Verification Map", "Edge Cases And Failure Modes", "Non-Goals And Forbidden Approaches", "Verification Commands"];
2
+ export declare const IMPLEMENTATION_CONTRACT_SUBSECTIONS: readonly ['User Outcome', 'Behavior Contract', 'File-Level Work Plan', 'Acceptance-To-Verification Map', 'Edge Cases And Failure Modes', 'Non-Goals And Forbidden Approaches', 'Verification Commands'];
3
3
  export declare function hasImplementationContract(specBody: string): boolean;
4
4
  export declare function extractTopLevelSection(body: string, sectionName: string): string;
5
5
  //# sourceMappingURL=common.d.ts.map
@@ -1,43 +1,33 @@
1
1
  import { hasImplementationContract, IMPLEMENTATION_CONTRACT_SECTION } from './common.js';
2
2
  export function buildImplementationContractSection(input) {
3
- const criteria = input.criteria.length > 0 ? input.criteria : fallbackCriteria();
3
+ const criteria = input.criteria;
4
4
  const testFiles = input.files.test.map((file) => file.path);
5
- const verificationCommands = input.verificationCommands.length > 0
6
- ? input.verificationCommands
7
- : ['pnpm typecheck', 'pnpm lint', 'pnpm test'];
5
+ const verificationCommands = input.verificationCommands;
8
6
  const lines = [
9
7
  `## ${IMPLEMENTATION_CONTRACT_SECTION}`,
10
8
  '### User Outcome',
11
9
  firstConcreteSentence(input.description) ??
12
- 'Needs decision: define the exact user-visible outcome this spec must deliver.',
10
+ 'Needs decision: define the exact observable outcome this spec must deliver.',
13
11
  '',
14
12
  '### Behavior Contract',
15
13
  ...renderBehaviorContract(criteria),
16
- '- Inputs: use the user request and existing project conventions as the source of truth.',
17
- '- Outputs: update canonical `spec.md` behavior without creating sidecar implementation docs.',
18
- '- State changes: persist only the files and metadata required by the spec workflow.',
19
- '- Errors: missing implementation detail must surface as `Needs decision:` instead of generic filler.',
20
14
  '',
21
15
  '### File-Level Work Plan',
22
16
  ...renderFilePlan(input.files),
23
17
  '',
24
- '### Minimal Change Guidance',
25
- ...renderMinimalChangeGuidance(),
26
- '',
27
18
  '### Acceptance-To-Verification Map',
28
19
  ...criteria.map((criterion, index) => renderVerificationRow(criterion.text, index + 1, testFiles, verificationCommands)),
29
20
  '',
30
21
  '### Edge Cases And Failure Modes',
31
- '- Generated specs with too little context must use `Needs decision:` placeholders.',
32
- '- Existing specs without this section must remain readable and must not be rewritten by readiness checks.',
33
- '- A heading with filler text must not count as a usable implementation contract.',
34
- '- Manual verification must include exact observable steps when no automated proof is practical.',
22
+ '- Needs decision: identify criterion-specific failure modes from user input or project evidence.',
35
23
  '',
36
24
  '### Non-Goals And Forbidden Approaches',
37
25
  ...renderNonGoals(input.outOfScope),
38
26
  '',
39
27
  '### Verification Commands',
40
- ...verificationCommands.map((command) => `- \`${command}\``),
28
+ ...(verificationCommands.length > 0
29
+ ? verificationCommands.map((command) => `- \`${command}\``)
30
+ : ['- Needs decision: provide a repository-supported verification command.']),
41
31
  '',
42
32
  ];
43
33
  return lines.join('\n');
@@ -48,21 +38,6 @@ export function appendImplementationContractIfMissing(specBody, input) {
48
38
  }
49
39
  return `${specBody.trimEnd()}\n\n${buildImplementationContractSection(input)}`;
50
40
  }
51
- function renderMinimalChangeGuidance() {
52
- return [
53
- '- Before implementing, check whether the work is unnecessary, already exists in this codebase, is covered by stdlib, is covered by the native platform, or is covered by an already-installed dependency.',
54
- '- Add new code only after those checks, and keep the diff to the minimum that satisfies the accepted criteria.',
55
- '- Do not use minimality to remove security, trust-boundary validation, data-loss handling, accessibility, explicit requirements, or required validation.',
56
- ];
57
- }
58
- function fallbackCriteria() {
59
- return [
60
- {
61
- text: 'Needs decision: define at least one acceptance criterion with executable verification.',
62
- done: false,
63
- },
64
- ];
65
- }
66
41
  function firstConcreteSentence(description) {
67
42
  const body = description
68
43
  .replace(/^---[\s\S]*?---/, '')
@@ -89,20 +64,23 @@ function renderFilePlan(files) {
89
64
  return lines.length > 0 ? lines : ['- Needs decision: identify expected source and test files.'];
90
65
  }
91
66
  function renderBehaviorContract(criteria) {
92
- return criteria.map((criterion, index) => `- Expected behavior AC${String(index + 1)}: ${criterion.text}`);
67
+ return criteria.length > 0
68
+ ? criteria.map((criterion, index) => `- Expected behavior AC${String(index + 1)}: ${criterion.text}`)
69
+ : ['- Needs decision: define at least one evidence-grounded acceptance criterion.'];
93
70
  }
94
71
  function renderVerificationRow(criterion, index, testFiles, commands) {
95
72
  const test = testFiles[0];
96
73
  if (test) {
97
- return `- AC${String(index)}: ${criterion} -> unit test \`${test}\` plus \`${commands[0] ?? 'pnpm test'}\`.`;
74
+ const command = commands[0];
75
+ return command
76
+ ? `- AC${String(index)}: ${criterion} -> unit test \`${test}\` plus \`${command}\`.`
77
+ : `- AC${String(index)}: ${criterion} -> unit test \`${test}\`.`;
98
78
  }
99
79
  return `- AC${String(index)}: ${criterion} -> Needs decision: choose unit test, integration test, CLI/tool result, snapshot/golden fixture, or exact manual verification.`;
100
80
  }
101
81
  function renderNonGoals(outOfScope) {
102
- const base = [
103
- '- Do not create durable sidecar implementation docs outside canonical `spec.md`.',
104
- '- Do not treat headings or filler text as implementation-ready detail.',
105
- ];
106
- return outOfScope.length > 0 ? [...base, ...outOfScope.map((item) => `- ${item}`)] : base;
82
+ return outOfScope.length > 0
83
+ ? outOfScope.map((item) => `- ${item}`)
84
+ : ['- Needs decision: provide explicit non-goals or confirm that none were supplied.'];
107
85
  }
108
86
  //# sourceMappingURL=renderer.js.map
@@ -3,13 +3,13 @@
3
3
  * "validate input") but rarely represent a domain concept worth searching for.
4
4
  * Keep lowercase; callers normalize before lookup.
5
5
  */
6
- export declare const ACTION_VERBS: readonly ["commit", "write", "read", "delete", "create", "update", "export", "import", "store", "send", "push", "pull", "sync", "check", "validate", "verify", "fetch", "load", "save", "remove", "add", "get", "set", "put", "post", "patch", "run", "execute", "parse", "render", "build", "compile"];
6
+ export declare const ACTION_VERBS: readonly ['commit', 'write', 'read', 'delete', 'create', 'update', 'export', 'import', 'store', 'send', 'push', 'pull', 'sync', 'check', 'validate', 'verify', 'fetch', 'load', 'save', 'remove', 'add', 'get', 'set', 'put', 'post', 'patch', 'run', 'execute', 'parse', 'render', 'build', 'compile'];
7
7
  /**
8
8
  * Generic English connectors and filler nouns that do not narrow a match.
9
9
  * Kept deliberately small — domain stopwords should go through
10
10
  * `KeywordMatchOptions.customStopwords`.
11
11
  */
12
- export declare const COMMON_STOPWORDS: readonly ["the", "and", "for", "that", "this", "with", "from", "into", "onto", "version", "control", "system", "data", "value", "field", "record", "user", "item", "thing", "stuff", "something", "anything"];
12
+ export declare const COMMON_STOPWORDS: readonly ['the', 'and', 'for', 'that', 'this', 'with', 'from', 'into', 'onto', 'version', 'control', 'system', 'data', 'value', 'field', 'record', 'user', 'item', 'thing', 'stuff', 'something', 'anything'];
13
13
  /**
14
14
  * True if `word` is a known action verb (case-insensitive).
15
15
  */
@@ -3,7 +3,7 @@ import type { SpecStatus, NextAction, LifecycleMap } from '../types/index.js';
3
3
  * Returns the guided next action for a given status transition.
4
4
  * The hint targets the *newStatus* reached, guiding what to do from there.
5
5
  */
6
- export declare function buildNextAction(newStatus: SpecStatus): NextAction;
6
+ export declare function buildNextAction(newStatus: SpecStatus): NextAction | null;
7
7
  /**
8
8
  * Returns the lifecycle map for the given current status.
9
9
  * Shows where the spec is, what's next, and what remains.
@@ -2,53 +2,37 @@
2
2
  const LIFECYCLE_STAGES = ['draft', 'review', 'approved', 'implementing', 'done'];
3
3
  /** Maps each forward transition to a guided next action */
4
4
  const NEXT_ACTION_MAP = {
5
+ draft: {
6
+ step: 'Challenge the spec',
7
+ prompt: 'Run challenge_spec and resolve the discovered findings before review',
8
+ suggestedCommand: 'challenge_spec(specId)',
9
+ whenToDoIt: 'Before requesting review',
10
+ },
5
11
  review: {
6
12
  step: 'Get approval',
7
13
  prompt: "Share this plan with your team or just tell me 'approved' to move forward",
8
- suggestedCommand: 'update_status(approved)',
14
+ suggestedCommand: 'update_status(status="approved")',
9
15
  whenToDoIt: "When you're happy with the plan",
10
16
  },
11
17
  approved: {
12
- step: 'Create your git branch',
13
- prompt: 'Run: git checkout -b feat/<spec-slug>',
14
- suggestedCommand: 'manage_git(branch)',
18
+ step: 'Start implementation',
19
+ prompt: 'Move the approved spec to implementing before writing production code',
20
+ suggestedCommand: 'update_status(status="implementing")',
15
21
  whenToDoIt: 'Before writing any code',
16
22
  },
17
23
  implementing: {
18
- step: 'Start coding',
19
- prompt: 'Implement the spec following the technical plan',
20
- suggestedCommand: 'validate(specId)',
21
- whenToDoIt: 'While developing',
22
- },
23
- done: {
24
- step: 'Validate your implementation',
25
- prompt: 'Run validate to check that your code matches the spec',
24
+ step: 'Validate the implementation',
25
+ prompt: 'Check the implementation against every acceptance criterion',
26
26
  suggestedCommand: 'validate(specId)',
27
27
  whenToDoIt: 'After all tests pass',
28
28
  },
29
- discarded: {
30
- step: 'Archive or delete spec',
31
- prompt: 'This spec has been discarded. You can delete it or keep it for reference.',
32
- suggestedCommand: 'delete_spec(specId)',
33
- whenToDoIt: 'When cleanup is needed',
34
- },
35
29
  };
36
30
  /**
37
31
  * Returns the guided next action for a given status transition.
38
32
  * The hint targets the *newStatus* reached, guiding what to do from there.
39
33
  */
40
34
  export function buildNextAction(newStatus) {
41
- const hint = NEXT_ACTION_MAP[newStatus];
42
- if (hint !== undefined) {
43
- return hint;
44
- }
45
- // Fallback for any unrecognised status
46
- return {
47
- step: 'Continue',
48
- prompt: `Spec is now ${newStatus}. Check Planu for next steps.`,
49
- suggestedCommand: 'list_specs()',
50
- whenToDoIt: 'When ready',
51
- };
35
+ return NEXT_ACTION_MAP[newStatus] ?? null;
52
36
  }
53
37
  /**
54
38
  * Returns the lifecycle map for the given current status.
@@ -5,7 +5,7 @@ import type { ProviderRenderInput, ProviderRenderOutput, SpecRenderer } from '..
5
5
  */
6
6
  export declare const CLAUDE_TOOL_CALLOUT_MAP: Readonly<Record<string, readonly string[]>>;
7
7
  export declare class ClaudeRenderer implements SpecRenderer {
8
- readonly kind: "claude";
8
+ readonly kind: 'claude';
9
9
  render(input: ProviderRenderInput): Promise<ProviderRenderOutput>;
10
10
  }
11
11
  //# sourceMappingURL=claude.d.ts.map
@@ -5,7 +5,7 @@ import type { ProviderRenderInput, ProviderRenderOutput, SpecRenderer } from '..
5
5
  */
6
6
  export declare const GEMINI_API_VERSION = "v1beta";
7
7
  export declare class GeminiRenderer implements SpecRenderer {
8
- readonly kind: "gemini";
8
+ readonly kind: 'gemini';
9
9
  render(input: ProviderRenderInput): Promise<ProviderRenderOutput>;
10
10
  }
11
11
  //# sourceMappingURL=gemini.d.ts.map
@@ -1,6 +1,6 @@
1
1
  import type { ProviderRenderInput, ProviderRenderOutput, SpecRenderer } from '../../../types/provider-adapter.js';
2
2
  export declare class GPT4Renderer implements SpecRenderer {
3
- readonly kind: "gpt4";
3
+ readonly kind: 'gpt4';
4
4
  render(input: ProviderRenderInput): Promise<ProviderRenderOutput>;
5
5
  }
6
6
  //# sourceMappingURL=gpt4.d.ts.map
@@ -1,6 +1,6 @@
1
1
  import type { ProviderRenderInput, ProviderRenderOutput, SpecRenderer } from '../../../types/provider-adapter.js';
2
2
  export declare class GenericMarkdownRenderer implements SpecRenderer {
3
- readonly kind: "markdown";
3
+ readonly kind: 'markdown';
4
4
  render(input: ProviderRenderInput): Promise<ProviderRenderOutput>;
5
5
  }
6
6
  //# sourceMappingURL=markdown.d.ts.map
@@ -6,7 +6,7 @@ import type { AtomicWriteOptions } from '../../types/safety.js';
6
6
  export declare class SpecFrozenError extends Error {
7
7
  readonly specId: string;
8
8
  readonly fixHint: string;
9
- readonly code: "SPEC_FROZEN";
9
+ readonly code: 'SPEC_FROZEN';
10
10
  constructor(specId: string, fixHint: string);
11
11
  }
12
12
  /**
@@ -3,7 +3,7 @@ declare const HEARTBEAT_INTERVAL_MS = 30000;
3
3
  export { HEARTBEAT_INTERVAL_MS };
4
4
  /** Thrown by withSpecLock when the lock is held by another live session. */
5
5
  export declare class LockBusyError extends Error {
6
- readonly code: "LOCK_BUSY";
6
+ readonly code: 'LOCK_BUSY';
7
7
  readonly holder: CrossProcessLockMetadata;
8
8
  constructor(holder: CrossProcessLockMetadata);
9
9
  }
@@ -1,6 +1,6 @@
1
1
  import type { SafePath } from '../../types/safety.js';
2
2
  export declare class PathViolationError extends Error {
3
- readonly code: "PATH_VIOLATION";
3
+ readonly code: 'PATH_VIOLATION';
4
4
  readonly input: string;
5
5
  constructor(message: string, input: string);
6
6
  }
@@ -1,28 +1,58 @@
1
1
  // engine/security-analyzer/profile-detector.ts — Feature profile and auth detection
2
2
  // SPEC-029
3
+ import { hasAnyAffirmedMatch } from '../text-signal-boundaries.js';
3
4
  // === Feature profile detection ===
4
- const PROFILE_KEYWORDS = [
5
- { keywords: ['api', 'endpoint', 'route', 'rest', 'graphql'], profile: 'public-api' },
5
+ const PROFILE_SIGNALS = [
6
6
  {
7
- keywords: ['auth', 'login', 'signup', 'register', 'password', 'jwt', 'oauth', 'session'],
7
+ patterns: [
8
+ /(?<!compiler\s)\bapi\b(?!\s+(?:compatibility|surface|package|type))/i,
9
+ /\b(?:public|external|rest|http)[ -]?api\b/i,
10
+ /\bapi\s+(?:endpoint|route|request|response|server)\b/i,
11
+ /\b(?:http|rest)\s+(?:endpoint|route)\b/i,
12
+ /\bgraphql\s+(?:api|endpoint|resolver|schema)\b/i,
13
+ ],
14
+ profile: 'public-api',
15
+ },
16
+ {
17
+ patterns: [
18
+ /\b(?:auth(?:entication)?|login|sign[ -]?up|register\s+user|password|jwt|oauth2?|session\s+auth)\b/i,
19
+ ],
8
20
  profile: 'auth',
9
21
  },
10
22
  {
11
- keywords: ['database', 'query', 'sql', 'db', 'schema', 'orm', 'repository'],
23
+ patterns: [
24
+ /\b(?:database|db-access|postgres(?:ql)?|mysql|sqlite|mongodb|dynamodb)\b/i,
25
+ /\b(?:sql|nosql)\s+(?:query|statement|table|database)\b/i,
26
+ /\b(?:database|persistence)\s+(?:query|schema|table|migration|transaction)\b/i,
27
+ /\b(?:orm|prisma|drizzle|typeorm|sequelize)\b/i,
28
+ ],
12
29
  profile: 'db-access',
13
30
  },
14
- { keywords: ['upload', 'file', 'attachment', 'media', 'image'], profile: 'file-upload' },
15
- { keywords: ['html', 'render', 'template', 'ui', 'frontend', 'view'], profile: 'html-render' },
16
31
  {
17
- keywords: ['pii', 'personal', 'email', 'phone', 'ssn', 'gdpr', 'privacy'],
32
+ patterns: [
33
+ /\bfile[ -]?upload\b/i,
34
+ /\b(?:upload|attach)(?:ed|ment|ing|s)?\s+(?:file|image|media)\b/i,
35
+ ],
36
+ profile: 'file-upload',
37
+ },
38
+ {
39
+ patterns: [
40
+ /\b(?:html\s+render|render(?:ed|ing)?\s+html|ui\s+(?:component|view|rendering)|frontend\s+view|template\s+render)\b/i,
41
+ ],
42
+ profile: 'html-render',
43
+ },
44
+ {
45
+ patterns: [
46
+ /\b(?:pii|personal\s+(?:data|information)|user\s+email|email\s+address|phone\s+number|ssn|gdpr|privacy)\b/i,
47
+ ],
18
48
  profile: 'pii-data',
19
49
  },
20
50
  ];
21
51
  export function detectSecurityProfiles(title, description, tags) {
22
- const lower = `${title} ${description} ${tags.join(' ')}`.toLowerCase();
52
+ const text = `${title}\n${description}\n${tags.join(' ')}`;
23
53
  const profiles = [];
24
- for (const { keywords, profile } of PROFILE_KEYWORDS) {
25
- if (keywords.some((kw) => lower.includes(kw))) {
54
+ for (const { patterns, profile } of PROFILE_SIGNALS) {
55
+ if (hasAnyAffirmedMatch(text, patterns)) {
26
56
  profiles.push(profile);
27
57
  }
28
58
  }
@@ -33,20 +63,22 @@ export function detectSecurityProfiles(title, description, tags) {
33
63
  }
34
64
  // === Auth mechanism detection ===
35
65
  export function detectAuthMechanism(description, tags) {
36
- const lower = `${description} ${tags.join(' ')}`.toLowerCase();
37
- if (lower.includes('jwt') || lower.includes('json web token')) {
66
+ const text = `${description}\n${tags.join(' ')}`;
67
+ if (hasAnyAffirmedMatch(text, [/\b(?:jwt|json\s+web\s+token)\b/i])) {
38
68
  return 'jwt';
39
69
  }
40
- if (lower.includes('oauth') || lower.includes('oauth2')) {
70
+ if (hasAnyAffirmedMatch(text, [/\boauth2?\b/i])) {
41
71
  return 'oauth2';
42
72
  }
43
- if (lower.includes('api key') || lower.includes('apikey') || lower.includes('api_key')) {
73
+ if (hasAnyAffirmedMatch(text, [/\bapi[ _-]?key\b/i])) {
44
74
  return 'api-key';
45
75
  }
46
- if (lower.includes('session') || lower.includes('cookie')) {
76
+ if (hasAnyAffirmedMatch(text, [
77
+ /\b(?:session[ -]?based\s+authentication|session\s+auth|session\s+cookie)\b/i,
78
+ ])) {
47
79
  return 'session';
48
80
  }
49
- if (lower.includes('basic auth') || lower.includes('basic authentication')) {
81
+ if (hasAnyAffirmedMatch(text, [/\bbasic\s+auth(?:entication)?\b/i])) {
50
82
  return 'basic';
51
83
  }
52
84
  return 'none';
@@ -1,7 +1,7 @@
1
1
  /** Spanish copulative verbs that indicate non-testable, state-describing criteria. */
2
- export declare const COPULA_VERBS_ES: readonly ["es", "son", "está", "están", "tiene", "tienen"];
2
+ export declare const COPULA_VERBS_ES: readonly ['es', 'son', 'está', 'están', 'tiene', 'tienen'];
3
3
  /** English copulative verbs that indicate non-testable, state-describing criteria. */
4
- export declare const COPULA_VERBS_EN: readonly ["is", "are", "has", "have", "exists", "contains"];
4
+ export declare const COPULA_VERBS_EN: readonly ['is', 'are', 'has', 'have', 'exists', 'contains'];
5
5
  export interface CopulaCheckResult {
6
6
  hit: boolean;
7
7
  verb?: string;
@@ -2,7 +2,7 @@ import type { LeanCriterion, LeanSpecInput } from '../../types/index.js';
2
2
  export type { LeanCriterion, LeanHistoryEntry, LeanSpecInput } from '../../types/index.js';
3
3
  /** Generate lean spec.md content: YAML frontmatter + user description only. */
4
4
  export declare function generateLeanSpecContent(input: LeanSpecInput): string;
5
- /** Extract acceptance criteria from description. Looks for checkbox patterns or generates a default.
6
- * Priority: checkboxes > GIVEN/WHEN/THEN > plain list items > fallback. */
5
+ /** Extract acceptance criteria explicitly present in the description.
6
+ * Priority: checkboxes > GIVEN/WHEN/THEN > plain list items. */
7
7
  export declare function extractCriteria(description: string): LeanCriterion[];
8
8
  //# sourceMappingURL=lean-spec-generator.d.ts.map
@@ -128,8 +128,8 @@ function buildBddLines(description, extraCriteria, criteriaOverride, scenarioTes
128
128
  }
129
129
  return renderBddScenariosYaml(scenarios);
130
130
  }
131
- /** Extract acceptance criteria from description. Looks for checkbox patterns or generates a default.
132
- * Priority: checkboxes > GIVEN/WHEN/THEN > plain list items > fallback. */
131
+ /** Extract acceptance criteria explicitly present in the description.
132
+ * Priority: checkboxes > GIVEN/WHEN/THEN > plain list items. */
133
133
  export function extractCriteria(description) {
134
134
  const checkboxCriteria = extractCheckboxCriteria(description);
135
135
  if (checkboxCriteria.length > 0) {
@@ -143,12 +143,7 @@ export function extractCriteria(description) {
143
143
  if (plainListCriteria.length > 0) {
144
144
  return plainListCriteria;
145
145
  }
146
- return [
147
- {
148
- text: 'Define acceptance criteria — autopilot could not infer testable behavior from the description',
149
- done: false,
150
- },
151
- ];
146
+ return [];
152
147
  }
153
148
  /** Extract checkbox-style criteria: `- [ ] text` or `- [x] text`. */
154
149
  function extractCheckboxCriteria(description) {
@@ -198,16 +193,20 @@ function extractGivenWhenThenCriteria(description) {
198
193
  /** Extract plain list items: `- text` or `* text` (no checkboxes).
199
194
  * Only used when no checkboxes or GIVEN/WHEN/THEN are found. */
200
195
  function extractPlainListCriteria(description) {
196
+ const acceptanceSection = /(?:^|\n)#{1,6}[ \t]+Acceptance Criteria[ \t]*\r?\n([\s\S]*?)(?=\r?\n#{1,6}[ \t]+|$)/i.exec(description)?.[1] ?? '';
197
+ if (acceptanceSection.length === 0) {
198
+ return [];
199
+ }
201
200
  const criteria = [];
202
201
  // Plain list: `- text` or `* text`, not followed by `[`
203
202
  const plainListRegex = /^[-*]\s+(?!\[)(.+)$/gm;
204
- let match = plainListRegex.exec(description);
203
+ let match = plainListRegex.exec(acceptanceSection);
205
204
  while (match) {
206
205
  const text = match[1]?.trim() ?? '';
207
206
  if (text) {
208
207
  criteria.push({ text, done: false });
209
208
  }
210
- match = plainListRegex.exec(description);
209
+ match = plainListRegex.exec(acceptanceSection);
211
210
  }
212
211
  return criteria;
213
212
  }
@@ -4,102 +4,102 @@
4
4
  */
5
5
  export declare const SPEC_METADATA_VALUE_POLICY: {
6
6
  readonly id: {
7
- readonly classification: "required";
7
+ readonly classification: 'required';
8
8
  readonly consumers: readonly [string, ...string[]];
9
9
  readonly purpose: string;
10
10
  };
11
11
  readonly title: {
12
- readonly classification: "required";
12
+ readonly classification: 'required';
13
13
  readonly consumers: readonly [string, ...string[]];
14
14
  readonly purpose: string;
15
15
  };
16
16
  readonly status: {
17
- readonly classification: "required";
17
+ readonly classification: 'required';
18
18
  readonly consumers: readonly [string, ...string[]];
19
19
  readonly purpose: string;
20
20
  };
21
21
  readonly type: {
22
- readonly classification: "required";
22
+ readonly classification: 'required';
23
23
  readonly consumers: readonly [string, ...string[]];
24
24
  readonly purpose: string;
25
25
  };
26
26
  readonly target: {
27
- readonly classification: "required";
27
+ readonly classification: 'required';
28
28
  readonly consumers: readonly [string, ...string[]];
29
29
  readonly purpose: string;
30
30
  };
31
31
  readonly scope: {
32
- readonly classification: "required";
32
+ readonly classification: 'required';
33
33
  readonly consumers: readonly [string, ...string[]];
34
34
  readonly purpose: string;
35
35
  };
36
36
  readonly difficulty: {
37
- readonly classification: "required";
37
+ readonly classification: 'required';
38
38
  readonly consumers: readonly [string, ...string[]];
39
39
  readonly purpose: string;
40
40
  };
41
41
  readonly risk: {
42
- readonly classification: "required";
42
+ readonly classification: 'required';
43
43
  readonly consumers: readonly [string, ...string[]];
44
44
  readonly purpose: string;
45
45
  };
46
46
  readonly branch: {
47
- readonly classification: "required";
47
+ readonly classification: 'required';
48
48
  readonly consumers: readonly [string, ...string[]];
49
49
  readonly purpose: string;
50
50
  };
51
51
  readonly created: {
52
- readonly classification: "required";
52
+ readonly classification: 'required';
53
53
  readonly consumers: readonly [string, ...string[]];
54
54
  readonly purpose: string;
55
55
  };
56
56
  readonly spec_format_version: {
57
- readonly classification: "required";
57
+ readonly classification: 'required';
58
58
  readonly consumers: readonly [string, ...string[]];
59
59
  readonly purpose: string;
60
60
  };
61
61
  readonly spec_version: {
62
- readonly classification: "required";
62
+ readonly classification: 'required';
63
63
  readonly consumers: readonly [string, ...string[]];
64
64
  readonly purpose: string;
65
65
  };
66
66
  readonly tags: {
67
- readonly classification: "conditional";
67
+ readonly classification: 'conditional';
68
68
  readonly consumers: readonly [string, ...string[]];
69
69
  readonly purpose: string;
70
70
  };
71
71
  readonly generation: {
72
- readonly classification: "conditional";
72
+ readonly classification: 'conditional';
73
73
  readonly consumers: readonly [string, ...string[]];
74
74
  readonly purpose: string;
75
75
  };
76
76
  readonly qualityWarnings: {
77
- readonly classification: "conditional";
77
+ readonly classification: 'conditional';
78
78
  readonly consumers: readonly [string, ...string[]];
79
79
  readonly purpose: string;
80
80
  };
81
81
  readonly grounding_required: {
82
- readonly classification: "conditional";
82
+ readonly classification: 'conditional';
83
83
  readonly consumers: readonly [string, ...string[]];
84
84
  readonly purpose: string;
85
85
  };
86
86
  readonly grounding: {
87
- readonly classification: "conditional";
87
+ readonly classification: 'conditional';
88
88
  readonly consumers: readonly [string, ...string[]];
89
89
  readonly purpose: string;
90
90
  };
91
91
  readonly criteria: {
92
- readonly classification: "conditional";
92
+ readonly classification: 'conditional';
93
93
  readonly consumers: readonly [string, ...string[]];
94
94
  readonly purpose: string;
95
95
  };
96
96
  readonly scenarios: {
97
- readonly classification: "conditional";
97
+ readonly classification: 'conditional';
98
98
  readonly consumers: readonly [string, ...string[]];
99
99
  readonly purpose: string;
100
100
  };
101
101
  readonly history: {
102
- readonly classification: "conditional";
102
+ readonly classification: 'conditional';
103
103
  readonly consumers: readonly [string, ...string[]];
104
104
  readonly purpose: string;
105
105
  };