@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.
- package/CHANGELOG.md +16 -0
- package/dist/config/elicitation-questions.json +5 -17
- package/dist/engine/cascade-hooks/registry.js +0 -2
- package/dist/engine/challenge-scenarios-loader.d.ts +3 -3
- package/dist/engine/compliance-injector.d.ts +3 -3
- package/dist/engine/dashboard/kanban-logic.d.ts +1 -1
- package/dist/engine/doc-generator/portal/risk-matrix-svg.d.ts +1 -1
- package/dist/engine/doc-generator/proposal/proposal-generator.d.ts +1 -1
- package/dist/engine/elicitation/answer-extractor.d.ts +1 -4
- package/dist/engine/elicitation/answer-extractor.js +197 -68
- package/dist/engine/elicitation/decision-gap-detector.js +59 -79
- package/dist/engine/elicitation/index.d.ts +0 -1
- package/dist/engine/elicitation/index.js +0 -1
- package/dist/engine/elicitation/question-generator.js +10 -3
- package/dist/engine/elicitation/question-grounding-gate.js +83 -34
- package/dist/engine/evidence-gates/lifecycle-gate.js +27 -14
- package/dist/engine/evidence-index/index-builder.js +7 -2
- package/dist/engine/frontmatter-sha/frozen-fields.d.ts +1 -1
- package/dist/engine/handoff-artifacts/schemas/red-tests-handoff.d.ts +2 -2
- package/dist/engine/handoff-artifacts/schemas.d.ts +22 -22
- package/dist/engine/human-summary.js +3 -3
- package/dist/engine/implementation-contract/common.d.ts +1 -1
- package/dist/engine/implementation-contract/renderer.js +17 -39
- package/dist/engine/keyword-matcher/stopwords.d.ts +2 -2
- package/dist/engine/lifecycle-hints.d.ts +1 -1
- package/dist/engine/lifecycle-hints.js +13 -29
- package/dist/engine/provider-adapters/adapters/claude.d.ts +1 -1
- package/dist/engine/provider-adapters/adapters/gemini.d.ts +1 -1
- package/dist/engine/provider-adapters/adapters/gpt4.d.ts +1 -1
- package/dist/engine/provider-adapters/adapters/markdown.d.ts +1 -1
- package/dist/engine/safety/atomic-write-file.d.ts +1 -1
- package/dist/engine/safety/cross-process-lock.d.ts +1 -1
- package/dist/engine/safety/path-sanitizer.d.ts +1 -1
- package/dist/engine/security-analyzer/profile-detector.js +48 -16
- package/dist/engine/spec-format/copula-blacklist.d.ts +2 -2
- package/dist/engine/spec-format/lean-spec-generator.d.ts +2 -2
- package/dist/engine/spec-format/lean-spec-generator.js +9 -10
- package/dist/engine/spec-format/metadata-value-policy.d.ts +20 -20
- package/dist/engine/spec-generator/fallback-generator.js +13 -20
- package/dist/engine/spec-grounding/contract.d.ts +5 -0
- package/dist/engine/spec-grounding/contract.js +10 -6
- package/dist/engine/spec-migrator/lean-migration.js +9 -16
- package/dist/engine/text-signal-boundaries.js +45 -12
- package/dist/engine/universal-rules/rules/planu-modes.js +0 -2
- package/dist/engine/web-fetcher/stack-advisor.d.ts +2 -2
- package/dist/engine/web-fetcher/stack-advisor.js +78 -17
- package/dist/engine/well-known/manifest-schema.d.ts +33 -33
- package/dist/engine/workers/schema.d.ts +3 -3
- package/dist/hosts/claude-code/ux/mcp-resources.d.ts +4 -4
- package/dist/hosts/codex/rest-shim.d.ts +1 -1
- package/dist/native/lightweight-command-catalog.d.ts +30 -30
- package/dist/server/openapi-spec.d.ts +132 -132
- package/dist/tools/challenge-spec/challenge-report.d.ts +13 -0
- package/dist/tools/challenge-spec/challenge-report.js +55 -0
- package/dist/tools/challenge-spec/event-challenge-scenarios.js +8 -27
- package/dist/tools/challenge-spec/resilience-challenge-scenarios-a.js +16 -32
- package/dist/tools/challenge-spec/resilience-challenge-scenarios-b.js +15 -20
- package/dist/tools/challenge-spec/scenario-collector.d.ts +13 -0
- package/dist/tools/challenge-spec/scenario-collector.js +86 -0
- package/dist/tools/challenge-spec/scenarios-data.js +10 -9
- package/dist/tools/challenge-spec/scenarios-failure.js +38 -54
- package/dist/tools/challenge-spec/scenarios-scale.js +14 -15
- package/dist/tools/challenge-spec/scenarios-security.d.ts +1 -1
- package/dist/tools/challenge-spec/scenarios-security.js +18 -11
- package/dist/tools/challenge-spec/scenarios-utils.d.ts +31 -5
- package/dist/tools/challenge-spec/scenarios-utils.js +102 -6
- package/dist/tools/challenge-spec/security-challenge-scenarios.d.ts +1 -1
- package/dist/tools/challenge-spec/security-challenge-scenarios.js +20 -25
- package/dist/tools/challenge-spec.js +46 -124
- package/dist/tools/clarify-requirements/multiple-choice.js +1 -1
- package/dist/tools/clarify-requirements/questions-context.js +11 -11
- package/dist/tools/clarify-requirements/questions.js +3 -23
- package/dist/tools/clarify-requirements.js +38 -0
- package/dist/tools/compliance-gate-handler.d.ts +1 -1
- package/dist/tools/create-spec/autopilot-analyzer.d.ts +1 -1
- package/dist/tools/create-spec/autopilot-analyzer.js +29 -54
- package/dist/tools/create-spec/post-creation.js +23 -4
- package/dist/tools/create-spec.js +51 -168
- package/dist/tools/delete-first/suggest-deletions.d.ts +1 -1
- package/dist/tools/elicit-requirements-handler.js +20 -12
- package/dist/tools/generate-batch-script.d.ts +2 -2
- package/dist/tools/init-project/rules-generator.js +0 -1
- package/dist/tools/oauth-handler.d.ts +1 -1
- package/dist/tools/register-sdd-tools.d.ts +1 -1
- package/dist/tools/register-spec-tools/core-spec-tools.js +11 -2
- package/dist/tools/render-spec-for-provider.d.ts +2 -2
- package/dist/tools/scan-orphan-spec-refs.d.ts +6 -6
- package/dist/tools/schemas/agents.d.ts +7 -7
- package/dist/tools/schemas/analysis.d.ts +11 -11
- package/dist/tools/schemas/delete.d.ts +5 -5
- package/dist/tools/schemas/github.d.ts +4 -4
- package/dist/tools/schemas/ide-config.d.ts +12 -12
- package/dist/tools/schemas/infra.d.ts +9 -9
- package/dist/tools/schemas/lifecycle.d.ts +50 -50
- package/dist/tools/schemas/llm-provider-schemas.d.ts +7 -7
- package/dist/tools/schemas/plugins-schemas.d.ts +12 -12
- package/dist/tools/schemas/project-graph.d.ts +6 -6
- package/dist/tools/schemas/rbac.d.ts +2 -2
- package/dist/tools/schemas/registry.d.ts +3 -3
- package/dist/tools/schemas/runtime-security.d.ts +2 -2
- package/dist/tools/schemas/session.d.ts +2 -2
- package/dist/tools/schemas/spec.d.ts +15 -15
- package/dist/tools/schemas/token-intelligence.d.ts +6 -6
- package/dist/tools/schemas/token-optimization.d.ts +2 -2
- package/dist/tools/schemas/workers-schema.d.ts +4 -4
- package/dist/tools/skill-bootstrap-handler.d.ts +7 -7
- package/dist/tools/suggest-stack.js +49 -13
- package/dist/tools/tool-registry/deprecated-stubs.d.ts +1 -1
- package/dist/tools/triage-request.d.ts +3 -3
- package/dist/tools/update-status/dod-gates.d.ts +8 -0
- package/dist/tools/update-status/dod-gates.js +68 -43
- package/dist/tools/update-status/file-sync.d.ts +1 -1
- package/dist/tools/update-status/file-sync.js +10 -45
- package/dist/tools/update-status/index.js +133 -97
- package/dist/tools/update-status/qa-gate.js +26 -0
- package/dist/tools/update-status/response-builder.d.ts +1 -1
- package/dist/tools/update-status/response-builder.js +11 -19
- package/dist/tools/update-status/transition-guard.d.ts +2 -2
- package/dist/tools/update-status/transition-guard.js +11 -8
- package/dist/tools/update-status-actions.d.ts +0 -1
- package/dist/tools/update-status-actions.js +10 -26
- package/dist/tools/update-status-convention-gate.d.ts +1 -6
- package/dist/tools/update-status-convention-gate.js +9 -33
- package/dist/tools/validate-lint.d.ts +1 -1
- package/dist/tools/validate-lint.js +39 -1
- package/dist/types/docs.d.ts +0 -4
- package/dist/types/env.d.ts +0 -4
- package/dist/types/estimation.d.ts +0 -2
- package/dist/types/multi-teammate-review.d.ts +4 -4
- package/dist/types/plugin-install.d.ts +7 -7
- package/dist/types/spec/core.d.ts +20 -0
- package/dist/types/spec/inputs.d.ts +1 -1
- package/dist/types/stack/index.d.ts +1 -1
- package/dist/types/stack/recommend.d.ts +10 -0
- package/package.json +21 -18
- package/planu-native.json +1 -1
- package/planu-plugin.json +1 -1
- package/src/i18n/messages/en.json +0 -72
- package/src/i18n/messages/es.json +0 -72
- package/src/i18n/messages/pt.json +0 -72
- package/dist/config/elicitation-dimensions.json +0 -51
- package/dist/engine/actuals-estimator.d.ts +0 -14
- package/dist/engine/actuals-estimator.js +0 -64
- package/dist/engine/cascade-hooks/hooks/living-specs.hook.d.ts +0 -3
- package/dist/engine/cascade-hooks/hooks/living-specs.hook.js +0 -19
- package/dist/engine/elicitation/non-interactive-defaults.d.ts +0 -9
- package/dist/engine/elicitation/non-interactive-defaults.js +0 -28
- package/dist/engine/elicitation/option-builder.d.ts +0 -9
- package/dist/engine/elicitation/option-builder.js +0 -85
- package/dist/engine/progress-writer.d.ts +0 -10
- package/dist/engine/progress-writer.js +0 -63
- package/dist/tools/clarify-requirements/interview-mode.d.ts +0 -42
- package/dist/tools/clarify-requirements/interview-mode.js +0 -342
- package/dist/tools/update-status/mode-hints.d.ts +0 -4
- package/dist/tools/update-status/mode-hints.js +0 -24
|
@@ -1,15 +1,19 @@
|
|
|
1
|
+
import { extractCriteria } from '../spec-format/lean-spec-generator.js';
|
|
1
2
|
export class FallbackGenerator {
|
|
2
3
|
generate(request) {
|
|
3
|
-
const sourceDescription = normalizeSourceDescription(request.description
|
|
4
|
-
const
|
|
4
|
+
const sourceDescription = normalizeSourceDescription(request.description);
|
|
5
|
+
const hasCriteria = extractCriteria(sourceDescription).length > 0;
|
|
5
6
|
const technicalSection = '';
|
|
6
7
|
const specBody = [
|
|
7
8
|
'## Problem',
|
|
8
9
|
sourceDescription,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
...(hasCriteria
|
|
11
|
+
? []
|
|
12
|
+
: [
|
|
13
|
+
'',
|
|
14
|
+
'## Missing Decisions',
|
|
15
|
+
'Needs decision: provide at least one explicit, verifiable acceptance criterion.',
|
|
16
|
+
]),
|
|
13
17
|
].join('\n');
|
|
14
18
|
return Promise.resolve({
|
|
15
19
|
specBody,
|
|
@@ -19,28 +23,17 @@ export class FallbackGenerator {
|
|
|
19
23
|
generatedAt: new Date().toISOString(),
|
|
20
24
|
},
|
|
21
25
|
qualityWarnings: [
|
|
22
|
-
'Fallback generator used. Source description was preserved;
|
|
26
|
+
'Fallback generator used. Source description was preserved; missing decisions require explicit user input.',
|
|
23
27
|
],
|
|
24
28
|
fallbackReason: 'No external model generator is configured for create_spec.',
|
|
25
29
|
});
|
|
26
30
|
}
|
|
27
31
|
}
|
|
28
|
-
function normalizeSourceDescription(description
|
|
32
|
+
function normalizeSourceDescription(description) {
|
|
29
33
|
const compact = description.replace(/\r\n/g, '\n').trim();
|
|
30
34
|
if (compact.length > 0) {
|
|
31
35
|
return compact;
|
|
32
36
|
}
|
|
33
|
-
return
|
|
34
|
-
}
|
|
35
|
-
function buildContextLine(request) {
|
|
36
|
-
const parts = [
|
|
37
|
-
request.projectContext?.language,
|
|
38
|
-
request.projectContext?.framework,
|
|
39
|
-
request.projectContext?.architecture,
|
|
40
|
-
].filter((part) => part !== undefined && part.trim().length > 0);
|
|
41
|
-
if (parts.length === 0) {
|
|
42
|
-
return 'Use the existing project architecture and conventions.';
|
|
43
|
-
}
|
|
44
|
-
return `Use the existing ${parts.join(' / ')} project conventions.`;
|
|
37
|
+
return 'Needs decision: provide the source requirement description.';
|
|
45
38
|
}
|
|
46
39
|
//# sourceMappingURL=fallback-generator.js.map
|
|
@@ -6,6 +6,11 @@ export declare function filterGroundedCriteria(criteria: string[]): string[];
|
|
|
6
6
|
export declare function buildCriterionGroundingRecords(args: {
|
|
7
7
|
criteria: string[];
|
|
8
8
|
userInput: string;
|
|
9
|
+
criterionEvidence?: {
|
|
10
|
+
criterion: string;
|
|
11
|
+
evidence: string[];
|
|
12
|
+
}[];
|
|
13
|
+
/** @deprecated Global evidence cannot ground a criterion. Use criterionEvidence. */
|
|
9
14
|
projectEvidence?: string[];
|
|
10
15
|
generatedEvidence?: string[];
|
|
11
16
|
}): CriterionGroundingRecord[];
|
|
@@ -23,8 +23,11 @@ export function filterGroundedCriteria(criteria) {
|
|
|
23
23
|
}
|
|
24
24
|
export function buildCriterionGroundingRecords(args) {
|
|
25
25
|
const userInput = normalizeCriterionText(args.userInput);
|
|
26
|
-
const projectEvidence = args.projectEvidence ?? [];
|
|
27
26
|
const generatedEvidence = args.generatedEvidence ?? [];
|
|
27
|
+
const criterionEvidence = new Map((args.criterionEvidence ?? []).map((record) => [
|
|
28
|
+
normalizeCriterionText(record.criterion),
|
|
29
|
+
record.evidence.filter((item) => item.trim().length > 0),
|
|
30
|
+
]));
|
|
28
31
|
return args.criteria.map((criterion) => {
|
|
29
32
|
const normalized = normalizeCriterionText(criterion);
|
|
30
33
|
if (isGenericCriterion(criterion)) {
|
|
@@ -43,12 +46,13 @@ export function buildCriterionGroundingRecords(args) {
|
|
|
43
46
|
confidence: 'high',
|
|
44
47
|
};
|
|
45
48
|
}
|
|
46
|
-
|
|
49
|
+
const directProjectEvidence = criterionEvidence.get(normalized) ?? [];
|
|
50
|
+
if (directProjectEvidence.length > 0) {
|
|
47
51
|
return {
|
|
48
52
|
text: criterion,
|
|
49
53
|
source: 'project_evidence',
|
|
50
|
-
evidence:
|
|
51
|
-
confidence: '
|
|
54
|
+
evidence: directProjectEvidence.slice(0, 8),
|
|
55
|
+
confidence: 'high',
|
|
52
56
|
};
|
|
53
57
|
}
|
|
54
58
|
return {
|
|
@@ -108,7 +112,7 @@ export function checkGroundedSpecContract(spec, content) {
|
|
|
108
112
|
issues.push(`Spec ${spec.id} requires grounding metadata but has no grounded criteria records.`);
|
|
109
113
|
}
|
|
110
114
|
for (const record of records) {
|
|
111
|
-
if (record.source === 'ungrounded_advisory') {
|
|
115
|
+
if (record.source === 'ungrounded_advisory' || record.source === 'documented_assumption') {
|
|
112
116
|
issues.push(`Criterion is advisory-only and cannot be approved as contract: ${record.text}`);
|
|
113
117
|
}
|
|
114
118
|
if (record.evidence.length === 0 && record.source !== 'ungrounded_advisory') {
|
|
@@ -117,7 +121,7 @@ export function checkGroundedSpecContract(spec, content) {
|
|
|
117
121
|
}
|
|
118
122
|
const technicalRecords = parseTechnicalReferenceGroundingRecords(frontmatter);
|
|
119
123
|
for (const record of technicalRecords) {
|
|
120
|
-
if (record.source === 'ungrounded_advisory') {
|
|
124
|
+
if (record.source === 'ungrounded_advisory' || record.source === 'documented_assumption') {
|
|
121
125
|
issues.push(`Technical reference is advisory-only and cannot be contract: ${record.path}`);
|
|
122
126
|
}
|
|
123
127
|
if (record.evidence.length === 0 && record.source !== 'ungrounded_advisory') {
|
|
@@ -113,9 +113,6 @@ function extractOldCriteria(content) {
|
|
|
113
113
|
}
|
|
114
114
|
match = re.exec(content);
|
|
115
115
|
}
|
|
116
|
-
if (criteria.length === 0) {
|
|
117
|
-
criteria.push({ text: 'Implementation complete and tested', done: false });
|
|
118
|
-
}
|
|
119
116
|
return criteria;
|
|
120
117
|
}
|
|
121
118
|
/** Check if a criterion is generic boilerplate (not specific to the feature). */
|
|
@@ -415,10 +412,14 @@ export async function migrateSpecToLean(specDir, projectPath) {
|
|
|
415
412
|
const criteria = extractOldCriteria(specContent);
|
|
416
413
|
const estimation = buildEstimationFromOld(metadata, oldTechContent);
|
|
417
414
|
const spec = buildSpecFromMetadata(metadata, id, title, status, estimation, specPath, techPath);
|
|
418
|
-
// Generate lean spec.md with extracted criteria
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
415
|
+
// Generate lean spec.md with extracted legacy criteria. Do not synthesize defaults:
|
|
416
|
+
// migrations preserve source-backed acceptance criteria only.
|
|
417
|
+
const leanSpecLines = generateLeanSpecContent({
|
|
418
|
+
spec,
|
|
419
|
+
description,
|
|
420
|
+
estimation,
|
|
421
|
+
criteriaOverride: criteria,
|
|
422
|
+
});
|
|
422
423
|
// Generate the legacy technical body, then fold it into unified spec.md.
|
|
423
424
|
const leanTech = generateLeanTechnicalContent({
|
|
424
425
|
specId: id,
|
|
@@ -434,7 +435,7 @@ export async function migrateSpecToLean(specDir, projectPath) {
|
|
|
434
435
|
return `backup failed: ${backupErr instanceof Error ? backupErr.message : String(backupErr)}`;
|
|
435
436
|
}
|
|
436
437
|
// Write one unified spec.md.
|
|
437
|
-
await atomicWriteFile(specPath, buildUnifiedSpecContent(
|
|
438
|
+
await atomicWriteFile(specPath, buildUnifiedSpecContent(leanSpecLines, leanTech));
|
|
438
439
|
// Delete obsolete files
|
|
439
440
|
for (const file of OBSOLETE_FILES) {
|
|
440
441
|
const filePath = join(specDir, file);
|
|
@@ -457,14 +458,6 @@ export async function migrateSpecToLean(specDir, projectPath) {
|
|
|
457
458
|
return err instanceof Error ? err.message : String(err);
|
|
458
459
|
}
|
|
459
460
|
}
|
|
460
|
-
/** Replace criteria in lean spec content with extracted old criteria. */
|
|
461
|
-
function replaceCriteria(content, criteria) {
|
|
462
|
-
// Find the criteria section in YAML and replace it
|
|
463
|
-
const criteriaLines = criteria.map((c) => ` - text: "${c.text.replace(/"/g, '\\"')}"\n done: ${String(c.done)}`);
|
|
464
|
-
const newCriteria = `criteria:\n${criteriaLines.join('\n')}`;
|
|
465
|
-
// Replace the existing criteria block
|
|
466
|
-
return content.replace(/criteria:\n(?: {2}- text:.*\n {4}done:.*\n?)+/, `${newCriteria}\n`);
|
|
467
|
-
}
|
|
468
461
|
/** Migrate all specs in a project to lean format. */
|
|
469
462
|
export async function migrateAllSpecsToLean(projectPath) {
|
|
470
463
|
const result = { migratedCount: 0, skippedCount: 0, errors: [] };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// engine/text-signal-boundaries.ts — Boundary-aware keyword matching for advisory signals.
|
|
2
|
-
const NEGATION_WINDOW_WORDS = 5;
|
|
3
2
|
const NEGATION_TERMS = new Set([
|
|
4
3
|
'no',
|
|
5
4
|
'not',
|
|
@@ -15,25 +14,57 @@ const NEGATION_TERMS = new Set([
|
|
|
15
14
|
'never',
|
|
16
15
|
]);
|
|
17
16
|
const OUT_OF_SCOPE_RE = /\b(?:out\s+of\s+scope|not\s+in\s+scope|no\s+scope|do\s+not|don't)\b/i;
|
|
17
|
+
const NON_AFFIRMING_CONTEXT_RE = /\b(?:false[ -]?positive|incorrect(?:ly)?|wrong(?:ly)?|irrelevant|unrelated|noise|repeated\s+failure)\b/i;
|
|
18
|
+
const EXCLUDED_SECTION_RE = /^(?:out\s+of\s+scope|non[ -]?goals?|examples?|anticipated\s+test\s+breaks?)\b/i;
|
|
19
|
+
function stripExcludedMarkdownSections(text) {
|
|
20
|
+
const lines = text.split('\n');
|
|
21
|
+
const kept = [];
|
|
22
|
+
let excludedLevel;
|
|
23
|
+
for (const line of lines) {
|
|
24
|
+
const heading = /^(#{1,6})\s+(.+?)\s*$/.exec(line);
|
|
25
|
+
if (heading) {
|
|
26
|
+
const marker = heading[1];
|
|
27
|
+
const title = heading[2];
|
|
28
|
+
if (marker === undefined || title === undefined) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const level = marker.length;
|
|
32
|
+
if (excludedLevel !== undefined && level <= excludedLevel) {
|
|
33
|
+
excludedLevel = undefined;
|
|
34
|
+
}
|
|
35
|
+
if (EXCLUDED_SECTION_RE.test(title)) {
|
|
36
|
+
excludedLevel = level;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (excludedLevel === undefined) {
|
|
41
|
+
kept.push(line);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return kept.join('\n');
|
|
45
|
+
}
|
|
18
46
|
/** Remove fenced code and quoted examples before keyword-driven inference. */
|
|
19
47
|
export function stripNonContractText(text) {
|
|
20
|
-
return text
|
|
48
|
+
return stripExcludedMarkdownSections(text)
|
|
21
49
|
.replace(/```[\s\S]*?```/g, ' ')
|
|
22
50
|
.replace(/`[^`\n]+`/g, ' ')
|
|
23
51
|
.replace(/"[^"\n]+"/g, ' ')
|
|
24
52
|
.replace(/'[^'\n]+'/g, ' ');
|
|
25
53
|
}
|
|
26
|
-
function
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
.split(/[^a-z0-9]+/i)
|
|
31
|
-
.filter(Boolean);
|
|
54
|
+
function clauseBefore(text, index) {
|
|
55
|
+
const before = text.slice(Math.max(0, index - 320), index);
|
|
56
|
+
const boundary = Math.max(before.lastIndexOf('\n'), before.lastIndexOf('.'), before.lastIndexOf(';'));
|
|
57
|
+
return before.slice(boundary + 1);
|
|
32
58
|
}
|
|
33
59
|
function isNegatedAt(text, index) {
|
|
34
|
-
const before =
|
|
35
|
-
|
|
36
|
-
|
|
60
|
+
const before = clauseBefore(text, index)
|
|
61
|
+
.replace(/\bnot\s+only\b/gi, '')
|
|
62
|
+
.toLowerCase();
|
|
63
|
+
const tokens = before.split(/[^a-z0-9]+/i).filter(Boolean);
|
|
64
|
+
return tokens.some((word) => NEGATION_TERMS.has(word));
|
|
65
|
+
}
|
|
66
|
+
function isNonAffirmingAt(text, index) {
|
|
67
|
+
return NON_AFFIRMING_CONTEXT_RE.test(clauseBefore(text, index));
|
|
37
68
|
}
|
|
38
69
|
function isOutOfScopeLine(line, indexInLine) {
|
|
39
70
|
return OUT_OF_SCOPE_RE.test(line.slice(0, Math.max(indexInLine, 0) + 80));
|
|
@@ -49,7 +80,9 @@ export function hasAffirmedMatch(text, pattern) {
|
|
|
49
80
|
const lineStart = cleaned.lastIndexOf('\n', index) + 1;
|
|
50
81
|
const lineEnd = cleaned.indexOf('\n', index);
|
|
51
82
|
const line = cleaned.slice(lineStart, lineEnd === -1 ? cleaned.length : lineEnd);
|
|
52
|
-
if (!isNegatedAt(cleaned, index) &&
|
|
83
|
+
if (!isNegatedAt(cleaned, index) &&
|
|
84
|
+
!isNonAffirmingAt(cleaned, index) &&
|
|
85
|
+
!isOutOfScopeLine(line, index - lineStart)) {
|
|
53
86
|
return true;
|
|
54
87
|
}
|
|
55
88
|
match = re.exec(cleaned);
|
|
@@ -21,7 +21,6 @@ Never suggest bypassPermissions — it disables all safety checks.
|
|
|
21
21
|
|
|
22
22
|
## How to apply
|
|
23
23
|
|
|
24
|
-
- After \`update_status\` returns \`suggestedClaudeMode\`, relay it to the user: "I recommend switching to \`/mode <mode>\`"
|
|
25
24
|
- NEVER switch modes automatically — always tell the user and let them decide
|
|
26
25
|
- Critical bash actions (git push, npm publish, rm -rf) require approval regardless of mode
|
|
27
26
|
`;
|
|
@@ -43,7 +42,6 @@ Auto-generated by \`init_project\`.
|
|
|
43
42
|
|
|
44
43
|
## How to apply
|
|
45
44
|
|
|
46
|
-
- After \`update_status\` completes, suggest the appropriate mode to the user
|
|
47
45
|
- NEVER switch modes automatically — always inform the user first
|
|
48
46
|
`;
|
|
49
47
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { StackRecommendation, SuggestStackResult } from '../../types/index.js';
|
|
1
|
+
import type { StackAdviceGrounding, StackRecommendation, SuggestStackResult } from '../../types/index.js';
|
|
2
2
|
export declare function verifyFrameworkVersion(framework: string): Promise<string | null>;
|
|
3
3
|
export declare function recommendStack(projectType: string, requirements: string[], constraints: string[]): StackRecommendation;
|
|
4
4
|
export declare function recommendAlternativeStack(projectType: string, _requirements: string[]): StackRecommendation;
|
|
@@ -11,5 +11,5 @@ export declare function buildMcpCompatibility(stack: StackRecommendation): {
|
|
|
11
11
|
compatible: boolean;
|
|
12
12
|
notes: string;
|
|
13
13
|
}[];
|
|
14
|
-
export declare function buildSuggestStackResult(projectType: string, requirements: string[], constraints: string[]): Promise<SuggestStackResult>;
|
|
14
|
+
export declare function buildSuggestStackResult(projectType: string, requirements: string[], constraints: string[]): Promise<SuggestStackResult & StackAdviceGrounding>;
|
|
15
15
|
//# sourceMappingURL=stack-advisor.d.ts.map
|
|
@@ -96,20 +96,24 @@ function getBaseRecommendations() {
|
|
|
96
96
|
},
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
|
-
function applyRequirementOverrides(rec,
|
|
99
|
+
function applyRequirementOverrides(rec, requirements, constraints) {
|
|
100
100
|
let result = rec;
|
|
101
|
-
if (
|
|
101
|
+
if (hasAffirmedTerm(requirements, /\bpython\b/i) ||
|
|
102
|
+
hasAffirmedTerm(requirements, /\bml\b/i) ||
|
|
103
|
+
hasAffirmedTerm(requirements, /\bai\b/i)) {
|
|
104
|
+
const requestsApi = hasAffirmedTerm(requirements, /\bapi\b/i);
|
|
102
105
|
result = {
|
|
103
106
|
...result,
|
|
104
107
|
language: 'python',
|
|
105
|
-
framework:
|
|
106
|
-
version:
|
|
107
|
-
ecosystem:
|
|
108
|
+
framework: requestsApi ? 'fastapi' : 'django',
|
|
109
|
+
version: requestsApi ? '0.100+' : '5+',
|
|
110
|
+
ecosystem: requestsApi
|
|
108
111
|
? ['fastapi', 'pydantic', 'sqlalchemy', 'alembic']
|
|
109
112
|
: ['django', 'django-rest-framework', 'celery', 'pytest'],
|
|
110
113
|
};
|
|
111
114
|
}
|
|
112
|
-
if (
|
|
115
|
+
if (hasAffirmedTerm(requirements, /\bgo(?:lang)?\b/i) ||
|
|
116
|
+
hasAffirmedTerm(requirements, /\bperformance\b/i)) {
|
|
113
117
|
result = {
|
|
114
118
|
...result,
|
|
115
119
|
language: 'go',
|
|
@@ -118,7 +122,7 @@ function applyRequirementOverrides(rec, reqStr, constStr) {
|
|
|
118
122
|
ecosystem: ['gin', 'gorm', 'wire', 'testify'],
|
|
119
123
|
};
|
|
120
124
|
}
|
|
121
|
-
if (
|
|
125
|
+
if (hasAffirmedTerm(requirements, /\bsupabase\b/i)) {
|
|
122
126
|
result = {
|
|
123
127
|
...result,
|
|
124
128
|
database: 'supabase',
|
|
@@ -126,22 +130,21 @@ function applyRequirementOverrides(rec, reqStr, constStr) {
|
|
|
126
130
|
auth: 'supabase-auth',
|
|
127
131
|
};
|
|
128
132
|
}
|
|
129
|
-
if (
|
|
133
|
+
if (hasAffirmedTerm(requirements, /\bfirebase\b/i)) {
|
|
130
134
|
result = { ...result, database: 'firebase', auth: 'firebase-auth' };
|
|
131
135
|
}
|
|
132
|
-
if (
|
|
136
|
+
if (constraints.some((constraint) => constraint.toLowerCase().includes('no-vendor-lock')) ||
|
|
137
|
+
hasAffirmedTerm(constraints, /\bself[- ]host(?:ed|ing)?\b/i)) {
|
|
133
138
|
result = { ...result, deployment: 'docker + railway', database: 'postgresql' };
|
|
134
139
|
}
|
|
135
140
|
return result;
|
|
136
141
|
}
|
|
137
142
|
export function recommendStack(projectType, requirements, constraints) {
|
|
138
|
-
const reqStr = requirements.join(' ').toLowerCase();
|
|
139
|
-
const constStr = constraints.join(' ').toLowerCase();
|
|
140
143
|
const recommendations = getBaseRecommendations();
|
|
141
144
|
const base =
|
|
142
145
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- 'web' key always exists in getBaseRecommendations()
|
|
143
146
|
recommendations[projectType] ?? recommendations.web;
|
|
144
|
-
return applyRequirementOverrides(base,
|
|
147
|
+
return applyRequirementOverrides(base, requirements, constraints);
|
|
145
148
|
}
|
|
146
149
|
export function recommendAlternativeStack(projectType, _requirements) {
|
|
147
150
|
const alternatives = {
|
|
@@ -208,23 +211,23 @@ export function buildWhyNotOthers(_projectType, chosenFramework) {
|
|
|
208
211
|
const allAlternatives = [
|
|
209
212
|
{
|
|
210
213
|
name: 'Ruby on Rails',
|
|
211
|
-
reason: '
|
|
214
|
+
reason: 'Optional candidate; compare it against the confirmed requirements before selection.',
|
|
212
215
|
},
|
|
213
216
|
{
|
|
214
217
|
name: 'PHP/Laravel',
|
|
215
|
-
reason: '
|
|
218
|
+
reason: 'Optional candidate; compare it against the confirmed requirements before selection.',
|
|
216
219
|
},
|
|
217
220
|
{
|
|
218
221
|
name: 'Java/Spring',
|
|
219
|
-
reason: '
|
|
222
|
+
reason: 'Optional candidate; compare it against the confirmed requirements before selection.',
|
|
220
223
|
},
|
|
221
224
|
{
|
|
222
225
|
name: 'C#/.NET',
|
|
223
|
-
reason: '
|
|
226
|
+
reason: 'Optional candidate; compare it against the confirmed requirements before selection.',
|
|
224
227
|
},
|
|
225
228
|
{
|
|
226
229
|
name: 'Elixir/Phoenix',
|
|
227
|
-
reason: '
|
|
230
|
+
reason: 'Optional candidate; compare it against the confirmed requirements before selection.',
|
|
228
231
|
},
|
|
229
232
|
];
|
|
230
233
|
return allAlternatives
|
|
@@ -307,6 +310,8 @@ export async function buildSuggestStackResult(projectType, requirements, constra
|
|
|
307
310
|
// Offline: keep static versions, mark confidence as low
|
|
308
311
|
confidence = 'low';
|
|
309
312
|
}
|
|
313
|
+
const detectedEvidence = collectDetectedEvidence(requirements, constraints);
|
|
314
|
+
const confirmedChoices = collectConfirmedChoices(requirements);
|
|
310
315
|
return {
|
|
311
316
|
primary,
|
|
312
317
|
alternative,
|
|
@@ -314,6 +319,62 @@ export async function buildSuggestStackResult(projectType, requirements, constra
|
|
|
314
319
|
mcpCompatibility: buildMcpCompatibility(primary),
|
|
315
320
|
lastChecked: new Date().toISOString(),
|
|
316
321
|
confidence,
|
|
322
|
+
selectionStatus: confirmedChoices.length > 0 ? 'partially-confirmed' : 'unconfirmed',
|
|
323
|
+
detectedEvidence,
|
|
324
|
+
confirmedChoices,
|
|
325
|
+
candidateNotice: 'Recommendations are optional candidates. No candidate is selected or persisted without user confirmation.',
|
|
317
326
|
};
|
|
318
327
|
}
|
|
328
|
+
const NEGATION_RE = /\b(?:no|not|never|without|exclude|excluding|forbid|forbidden|avoid|must not|should not|do not|does not|don't|doesn't|out[- ]of[- ]scope)\b/i;
|
|
329
|
+
const NEGATIVE_SUFFIX_RE = /\b(?:forbidden|excluded|not allowed|out[- ]of[- ]scope)\b/i;
|
|
330
|
+
function hasAffirmedTerm(values, pattern) {
|
|
331
|
+
return values.some((value) => {
|
|
332
|
+
const withoutQuotedText = value
|
|
333
|
+
.replace(/```[\s\S]*?```/g, ' ')
|
|
334
|
+
.replace(/`[^`]+`/g, ' ')
|
|
335
|
+
.replace(/"[^"]+"|'[^']+'/g, ' ');
|
|
336
|
+
return withoutQuotedText.split(/(?:\r?\n|[.,;!?]+)/).some((segment) => {
|
|
337
|
+
const match = new RegExp(pattern.source, pattern.flags.replace('g', '')).exec(segment);
|
|
338
|
+
if (!match) {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
const prefix = segment.slice(0, match.index);
|
|
342
|
+
const suffix = segment.slice(match.index + match[0].length);
|
|
343
|
+
return !NEGATION_RE.test(prefix) && !NEGATIVE_SUFFIX_RE.test(suffix);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
function collectDetectedEvidence(requirements, constraints) {
|
|
348
|
+
const definitions = [
|
|
349
|
+
['language:python', /\bpython\b/i],
|
|
350
|
+
['language:go', /\bgo(?:lang)?\b/i],
|
|
351
|
+
['database:supabase', /\bsupabase\b/i],
|
|
352
|
+
['database:firebase', /\bfirebase\b/i],
|
|
353
|
+
['capability:api', /\bapi\b/i],
|
|
354
|
+
['capability:ml', /\b(?:ml|ai)\b/i],
|
|
355
|
+
];
|
|
356
|
+
const evidence = definitions
|
|
357
|
+
.filter(([, pattern]) => hasAffirmedTerm(requirements, pattern))
|
|
358
|
+
.map(([name]) => `requirement:${name}`);
|
|
359
|
+
if (constraints.some((constraint) => constraint.toLowerCase().includes('no-vendor-lock'))) {
|
|
360
|
+
evidence.push('constraint:no-vendor-lock');
|
|
361
|
+
}
|
|
362
|
+
if (hasAffirmedTerm(constraints, /\bself[- ]host(?:ed|ing)?\b/i)) {
|
|
363
|
+
evidence.push('constraint:self-host');
|
|
364
|
+
}
|
|
365
|
+
return evidence;
|
|
366
|
+
}
|
|
367
|
+
function collectConfirmedChoices(requirements) {
|
|
368
|
+
const confirmationText = requirements.join(' ');
|
|
369
|
+
const choices = [
|
|
370
|
+
['language', 'python', /\b(?:must use|use|selected|confirmed|requires?)\s+python\b/i],
|
|
371
|
+
['language', 'go', /\b(?:must use|use|selected|confirmed|requires?)\s+go(?:lang)?\b/i],
|
|
372
|
+
['database', 'supabase', /\b(?:must use|use|selected|confirmed|requires?)\s+supabase\b/i],
|
|
373
|
+
['database', 'firebase', /\b(?:must use|use|selected|confirmed|requires?)\s+firebase\b/i],
|
|
374
|
+
];
|
|
375
|
+
return choices
|
|
376
|
+
.filter(([, , pattern]) => pattern.test(confirmationText))
|
|
377
|
+
.filter(([, value]) => hasAffirmedTerm(requirements, new RegExp(`\\b${value}\\b`, 'i')))
|
|
378
|
+
.map(([field, value]) => ({ field, value, source: 'user_confirmation' }));
|
|
379
|
+
}
|
|
319
380
|
//# sourceMappingURL=stack-advisor.js.map
|
|
@@ -6,87 +6,87 @@ export type { KnownCapability };
|
|
|
6
6
|
* RFC 8615 .well-known URI pattern — follows OpenID Connect / OAuth conventions.
|
|
7
7
|
*/
|
|
8
8
|
export declare const MANIFEST_SCHEMA_V1: {
|
|
9
|
-
readonly $schema:
|
|
10
|
-
readonly $id:
|
|
11
|
-
readonly title:
|
|
12
|
-
readonly description:
|
|
13
|
-
readonly type:
|
|
14
|
-
readonly required: readonly [
|
|
9
|
+
readonly $schema: 'http://json-schema.org/draft-07/schema#';
|
|
10
|
+
readonly $id: 'https://planu.dev/schemas/well-known-manifest-v1.json';
|
|
11
|
+
readonly title: 'Planu .well-known Manifest v1';
|
|
12
|
+
readonly description: 'Spec registry discovery manifest published at .well-known/planu.json';
|
|
13
|
+
readonly type: 'object';
|
|
14
|
+
readonly required: readonly ['version', 'name', 'specCount', 'specs', 'capabilities', 'generatedAt'];
|
|
15
15
|
readonly additionalProperties: false;
|
|
16
16
|
readonly properties: {
|
|
17
17
|
readonly version: {
|
|
18
|
-
readonly type:
|
|
18
|
+
readonly type: 'number';
|
|
19
19
|
readonly const: 1;
|
|
20
|
-
readonly description:
|
|
20
|
+
readonly description: 'Manifest schema version. Always 1 for this format.';
|
|
21
21
|
};
|
|
22
22
|
readonly name: {
|
|
23
|
-
readonly type:
|
|
23
|
+
readonly type: 'string';
|
|
24
24
|
readonly minLength: 1;
|
|
25
25
|
readonly maxLength: 200;
|
|
26
|
-
readonly description:
|
|
26
|
+
readonly description: 'Project display name.';
|
|
27
27
|
};
|
|
28
28
|
readonly specCount: {
|
|
29
|
-
readonly type:
|
|
29
|
+
readonly type: 'integer';
|
|
30
30
|
readonly minimum: 0;
|
|
31
|
-
readonly description:
|
|
31
|
+
readonly description: 'Total number of specs included in this manifest.';
|
|
32
32
|
};
|
|
33
33
|
readonly specs: {
|
|
34
|
-
readonly type:
|
|
35
|
-
readonly description:
|
|
34
|
+
readonly type: 'array';
|
|
35
|
+
readonly description: 'Array of spec summaries.';
|
|
36
36
|
readonly items: {
|
|
37
|
-
readonly type:
|
|
38
|
-
readonly required: readonly [
|
|
37
|
+
readonly type: 'object';
|
|
38
|
+
readonly required: readonly ['id', 'title', 'status', 'type', 'description', 'tags', 'created'];
|
|
39
39
|
readonly additionalProperties: false;
|
|
40
40
|
readonly properties: {
|
|
41
41
|
readonly id: {
|
|
42
|
-
readonly type:
|
|
43
|
-
readonly pattern:
|
|
42
|
+
readonly type: 'string';
|
|
43
|
+
readonly pattern: '^SPEC-\\d+$';
|
|
44
44
|
};
|
|
45
45
|
readonly title: {
|
|
46
|
-
readonly type:
|
|
46
|
+
readonly type: 'string';
|
|
47
47
|
readonly maxLength: 300;
|
|
48
48
|
};
|
|
49
49
|
readonly status: {
|
|
50
|
-
readonly type:
|
|
50
|
+
readonly type: 'string';
|
|
51
51
|
readonly maxLength: 50;
|
|
52
52
|
};
|
|
53
53
|
readonly type: {
|
|
54
|
-
readonly type:
|
|
54
|
+
readonly type: 'string';
|
|
55
55
|
readonly maxLength: 50;
|
|
56
56
|
};
|
|
57
57
|
readonly description: {
|
|
58
|
-
readonly type:
|
|
58
|
+
readonly type: 'string';
|
|
59
59
|
readonly maxLength: 200;
|
|
60
60
|
};
|
|
61
61
|
readonly tags: {
|
|
62
|
-
readonly type:
|
|
62
|
+
readonly type: 'array';
|
|
63
63
|
readonly items: {
|
|
64
|
-
readonly type:
|
|
64
|
+
readonly type: 'string';
|
|
65
65
|
readonly maxLength: 100;
|
|
66
66
|
};
|
|
67
67
|
};
|
|
68
68
|
readonly created: {
|
|
69
|
-
readonly type:
|
|
70
|
-
readonly format:
|
|
69
|
+
readonly type: 'string';
|
|
70
|
+
readonly format: 'date';
|
|
71
71
|
};
|
|
72
72
|
};
|
|
73
73
|
};
|
|
74
74
|
};
|
|
75
75
|
readonly capabilities: {
|
|
76
|
-
readonly type:
|
|
77
|
-
readonly description:
|
|
76
|
+
readonly type: 'array';
|
|
77
|
+
readonly description: 'Planu capabilities exported by this project.';
|
|
78
78
|
readonly items: {
|
|
79
|
-
readonly type:
|
|
79
|
+
readonly type: 'string';
|
|
80
80
|
readonly maxLength: 100;
|
|
81
81
|
};
|
|
82
82
|
};
|
|
83
83
|
readonly generatedAt: {
|
|
84
|
-
readonly type:
|
|
85
|
-
readonly format:
|
|
86
|
-
readonly description:
|
|
84
|
+
readonly type: 'string';
|
|
85
|
+
readonly format: 'date-time';
|
|
86
|
+
readonly description: 'ISO 8601 timestamp when this manifest was generated.';
|
|
87
87
|
};
|
|
88
88
|
};
|
|
89
89
|
};
|
|
90
90
|
/** Known capabilities that a project can declare in its manifest. */
|
|
91
|
-
export declare const KNOWN_CAPABILITIES: readonly [
|
|
91
|
+
export declare const KNOWN_CAPABILITIES: readonly ['spec-registry', 'federation', 'living-spec', 'ai-analysis', 'estimation', 'changelog'];
|
|
92
92
|
//# sourceMappingURL=manifest-schema.d.ts.map
|
|
@@ -2,8 +2,8 @@ import { z } from 'zod';
|
|
|
2
2
|
export declare const WorkerTriggerSchema: z.ZodObject<{
|
|
3
3
|
type: z.ZodEnum<{
|
|
4
4
|
event: "event";
|
|
5
|
-
schedule: "schedule";
|
|
6
5
|
"file-change": "file-change";
|
|
6
|
+
schedule: "schedule";
|
|
7
7
|
}>;
|
|
8
8
|
patterns: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
9
9
|
events: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -15,8 +15,8 @@ export declare const WorkerDefinitionSchema: z.ZodObject<{
|
|
|
15
15
|
trigger: z.ZodObject<{
|
|
16
16
|
type: z.ZodEnum<{
|
|
17
17
|
event: "event";
|
|
18
|
-
schedule: "schedule";
|
|
19
18
|
"file-change": "file-change";
|
|
19
|
+
schedule: "schedule";
|
|
20
20
|
}>;
|
|
21
21
|
patterns: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
22
22
|
events: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -38,8 +38,8 @@ export declare const WorkerRegistrySchema: z.ZodObject<{
|
|
|
38
38
|
trigger: z.ZodObject<{
|
|
39
39
|
type: z.ZodEnum<{
|
|
40
40
|
event: "event";
|
|
41
|
-
schedule: "schedule";
|
|
42
41
|
"file-change": "file-change";
|
|
42
|
+
schedule: "schedule";
|
|
43
43
|
}>;
|
|
44
44
|
patterns: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
45
45
|
events: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -2,10 +2,10 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
2
2
|
import type { McpResourceMeta } from '../../../types/claude-code-ux.js';
|
|
3
3
|
/** Planu-namespaced resource URIs exposed by SPEC-588. */
|
|
4
4
|
export declare const PLANU_UX_RESOURCE_URIS: {
|
|
5
|
-
readonly specList:
|
|
6
|
-
readonly progress:
|
|
7
|
-
readonly constitution:
|
|
8
|
-
readonly recentActivity:
|
|
5
|
+
readonly specList: 'planu://ux/specs';
|
|
6
|
+
readonly progress: 'planu://ux/progress';
|
|
7
|
+
readonly constitution: 'planu://ux/constitution';
|
|
8
|
+
readonly recentActivity: 'planu://ux/recent-activity';
|
|
9
9
|
};
|
|
10
10
|
export declare const specListResourceMeta: McpResourceMeta;
|
|
11
11
|
export declare const progressResourceMeta: McpResourceMeta;
|