@planu/cli 4.11.1 → 4.11.2
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 +9 -0
- 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-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/keyword-matcher/stopwords.d.ts +2 -2
- 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/metadata-value-policy.d.ts +20 -20
- package/dist/engine/text-signal-boundaries.js +45 -12
- package/dist/engine/universal-rules/rules/planu-modes.js +0 -2
- 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/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 +39 -118
- package/dist/tools/compliance-gate-handler.d.ts +1 -1
- package/dist/tools/create-spec.js +4 -24
- package/dist/tools/delete-first/suggest-deletions.d.ts +1 -1
- 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/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/tool-registry/deprecated-stubs.d.ts +1 -1
- package/dist/tools/triage-request.d.ts +3 -3
- 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 +28 -27
- 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.js +1 -1
- 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/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/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/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/update-status/mode-hints.d.ts +0 -4
- package/dist/tools/update-status/mode-hints.js +0 -24
|
@@ -2,11 +2,11 @@ import { z } from 'zod';
|
|
|
2
2
|
export declare const SecurityReportTimeRangeEnum: z.ZodEnum<{
|
|
3
3
|
all: "all";
|
|
4
4
|
last24h: "last24h";
|
|
5
|
-
last7d: "last7d";
|
|
6
5
|
last30d: "last30d";
|
|
6
|
+
last7d: "last7d";
|
|
7
7
|
}>;
|
|
8
8
|
export declare const SecurityReportFormatEnum: z.ZodEnum<{
|
|
9
|
-
summary: "summary";
|
|
10
9
|
detailed: "detailed";
|
|
10
|
+
summary: "summary";
|
|
11
11
|
}>;
|
|
12
12
|
//# sourceMappingURL=runtime-security.d.ts.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export declare const SessionFormatEnum: z.ZodEnum<{
|
|
3
|
-
markdown: "markdown";
|
|
4
3
|
json: "json";
|
|
4
|
+
markdown: "markdown";
|
|
5
5
|
}>;
|
|
6
6
|
export declare const SessionHandoffSchema: {
|
|
7
7
|
readonly projectPath: z.ZodString;
|
|
@@ -19,8 +19,8 @@ export declare const ExportSessionSchema: {
|
|
|
19
19
|
readonly projectPath: z.ZodString;
|
|
20
20
|
readonly sessionId: z.ZodOptional<z.ZodString>;
|
|
21
21
|
readonly format: z.ZodOptional<z.ZodEnum<{
|
|
22
|
-
markdown: "markdown";
|
|
23
22
|
json: "json";
|
|
23
|
+
markdown: "markdown";
|
|
24
24
|
}>>;
|
|
25
25
|
};
|
|
26
26
|
export declare const AutoCheckpointSchema: {
|
|
@@ -5,43 +5,43 @@ export declare const SupportedLocaleEnum: z.ZodEnum<{
|
|
|
5
5
|
pt: "pt";
|
|
6
6
|
}>;
|
|
7
7
|
export declare const SpecStatusEnum: z.ZodEnum<{
|
|
8
|
-
draft: "draft";
|
|
9
|
-
review: "review";
|
|
10
8
|
approved: "approved";
|
|
11
|
-
implementing: "implementing";
|
|
12
|
-
done: "done";
|
|
13
9
|
discarded: "discarded";
|
|
10
|
+
done: "done";
|
|
11
|
+
draft: "draft";
|
|
12
|
+
implementing: "implementing";
|
|
13
|
+
review: "review";
|
|
14
14
|
}>;
|
|
15
15
|
export declare const SpecTypeEnum: z.ZodEnum<{
|
|
16
|
-
|
|
17
|
-
refactor: "refactor";
|
|
16
|
+
agent: "agent";
|
|
18
17
|
bugfix: "bugfix";
|
|
19
|
-
infra: "infra";
|
|
20
18
|
docs: "docs";
|
|
19
|
+
feature: "feature";
|
|
20
|
+
infra: "infra";
|
|
21
21
|
project: "project";
|
|
22
|
-
|
|
22
|
+
refactor: "refactor";
|
|
23
23
|
"tech-debt": "tech-debt";
|
|
24
24
|
}>;
|
|
25
25
|
export declare const SpecScopeEnum: z.ZodEnum<{
|
|
26
|
+
architectural: "architectural";
|
|
27
|
+
"cross-module": "cross-module";
|
|
26
28
|
feature: "feature";
|
|
27
29
|
trivial: "trivial";
|
|
28
|
-
"cross-module": "cross-module";
|
|
29
|
-
architectural: "architectural";
|
|
30
30
|
}>;
|
|
31
31
|
export declare const SpecTargetEnum: z.ZodEnum<{
|
|
32
|
-
|
|
32
|
+
android: "android";
|
|
33
33
|
backend: "backend";
|
|
34
|
-
|
|
34
|
+
database: "database";
|
|
35
|
+
frontend: "frontend";
|
|
35
36
|
fullstack: "fullstack";
|
|
36
37
|
infrastructure: "infrastructure";
|
|
37
|
-
database: "database";
|
|
38
38
|
ios: "ios";
|
|
39
|
-
|
|
39
|
+
shared: "shared";
|
|
40
40
|
}>;
|
|
41
41
|
export declare const ExperienceLevelEnum: z.ZodEnum<{
|
|
42
42
|
beginner: "beginner";
|
|
43
|
-
intermediate: "intermediate";
|
|
44
43
|
expert: "expert";
|
|
44
|
+
intermediate: "intermediate";
|
|
45
45
|
}>;
|
|
46
46
|
/** SPEC-313: Work mode enum for init_project and set_work_mode */
|
|
47
47
|
export declare const WorkModeEnum: z.ZodEnum<{
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export declare const TokenIntelligencePeriodEnum: z.ZodEnum<{
|
|
3
|
+
all_time: "all_time";
|
|
4
|
+
month: "month";
|
|
3
5
|
today: "today";
|
|
4
6
|
week: "week";
|
|
5
|
-
month: "month";
|
|
6
|
-
all_time: "all_time";
|
|
7
7
|
}>;
|
|
8
8
|
export declare const TokenIntelligenceGroupByEnum: z.ZodEnum<{
|
|
9
|
+
day: "day";
|
|
9
10
|
model: "model";
|
|
10
11
|
spec: "spec";
|
|
11
12
|
tool: "tool";
|
|
12
|
-
day: "day";
|
|
13
13
|
}>;
|
|
14
14
|
export declare const TokenIntelligenceViewEnum: z.ZodEnum<{
|
|
15
|
-
|
|
16
|
-
summary: "summary";
|
|
15
|
+
autopilot: "autopilot";
|
|
17
16
|
budget: "budget";
|
|
18
17
|
detailed: "detailed";
|
|
19
|
-
|
|
18
|
+
graph: "graph";
|
|
20
19
|
reconciliation: "reconciliation";
|
|
20
|
+
summary: "summary";
|
|
21
21
|
trends: "trends";
|
|
22
22
|
}>;
|
|
23
23
|
//# sourceMappingURL=token-intelligence.d.ts.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export declare const TokenUsagePeriodEnum: z.ZodEnum<{
|
|
3
|
-
|
|
3
|
+
all_time: "all_time";
|
|
4
4
|
current_session: "current_session";
|
|
5
5
|
last_hour: "last_hour";
|
|
6
|
-
|
|
6
|
+
today: "today";
|
|
7
7
|
}>;
|
|
8
8
|
export declare const TokenUsageGroupByEnum: z.ZodEnum<{
|
|
9
9
|
session: "session";
|
|
@@ -8,13 +8,13 @@ export declare const ConfigureWorkersInputSchema: z.ZodObject<{
|
|
|
8
8
|
projectId: z.ZodString;
|
|
9
9
|
workerName: z.ZodString;
|
|
10
10
|
action: z.ZodEnum<{
|
|
11
|
-
|
|
11
|
+
"add-pattern": "add-pattern";
|
|
12
12
|
disable: "disable";
|
|
13
|
+
enable: "enable";
|
|
14
|
+
"enable-auto-memory": "enable-auto-memory";
|
|
15
|
+
"remove-pattern": "remove-pattern";
|
|
13
16
|
"set-cooldown": "set-cooldown";
|
|
14
17
|
"set-priority": "set-priority";
|
|
15
|
-
"add-pattern": "add-pattern";
|
|
16
|
-
"remove-pattern": "remove-pattern";
|
|
17
|
-
"enable-auto-memory": "enable-auto-memory";
|
|
18
18
|
}>;
|
|
19
19
|
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
20
20
|
}, z.core.$strip>;
|
|
@@ -5,20 +5,20 @@ export declare const DiscoverSkillsInputSchema: {
|
|
|
5
5
|
stack: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
6
6
|
sources: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
7
7
|
"awesome-cursorrules": "awesome-cursorrules";
|
|
8
|
-
"skills-sh": "skills-sh";
|
|
9
8
|
"github-custom": "github-custom";
|
|
9
|
+
"skills-sh": "skills-sh";
|
|
10
10
|
}>>>;
|
|
11
11
|
};
|
|
12
12
|
export declare const BootstrapSkillsInputSchema: {
|
|
13
13
|
projectPath: z.ZodString;
|
|
14
14
|
stack: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
15
15
|
targets: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
16
|
+
"agents-md": "agents-md";
|
|
16
17
|
"claude-md": "claude-md";
|
|
18
|
+
clinerules: "clinerules";
|
|
17
19
|
cursorrules: "cursorrules";
|
|
18
|
-
windsurfrules: "windsurfrules";
|
|
19
20
|
"kiro-steering": "kiro-steering";
|
|
20
|
-
|
|
21
|
-
clinerules: "clinerules";
|
|
21
|
+
windsurfrules: "windsurfrules";
|
|
22
22
|
}>>>;
|
|
23
23
|
dryRun: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
24
24
|
};
|
|
@@ -34,12 +34,12 @@ export declare const ConfigureSkillRegistriesInputSchema: {
|
|
|
34
34
|
enableSource: z.ZodOptional<z.ZodString>;
|
|
35
35
|
disableSource: z.ZodOptional<z.ZodString>;
|
|
36
36
|
setTargets: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
37
|
+
"agents-md": "agents-md";
|
|
37
38
|
"claude-md": "claude-md";
|
|
39
|
+
clinerules: "clinerules";
|
|
38
40
|
cursorrules: "cursorrules";
|
|
39
|
-
windsurfrules: "windsurfrules";
|
|
40
41
|
"kiro-steering": "kiro-steering";
|
|
41
|
-
|
|
42
|
-
clinerules: "clinerules";
|
|
42
|
+
windsurfrules: "windsurfrules";
|
|
43
43
|
}>>>;
|
|
44
44
|
setAutoMerge: z.ZodOptional<z.ZodBoolean>;
|
|
45
45
|
};
|
|
@@ -5,7 +5,7 @@ export declare const DEPRECATION_MESSAGE_TEMPLATE = "This tool has been migrated
|
|
|
5
5
|
* The 5 MCP tool names that have been migrated to skills.
|
|
6
6
|
* Used in smoke tests (Scenario 6) and anywhere a guard on migrated tools is needed.
|
|
7
7
|
*/
|
|
8
|
-
export declare const DEPRECATED_TOOLS: readonly [
|
|
8
|
+
export declare const DEPRECATED_TOOLS: readonly ['execute_sdd_flow', 'implement_plan', 'multi_teammate_review', 'run_healing_loop', 'generate_orchestration_plan'];
|
|
9
9
|
export type DeprecatedToolName = (typeof DEPRECATED_TOOLS)[number];
|
|
10
10
|
/**
|
|
11
11
|
* Create a ToolEntry-compatible deprecation stub.
|
|
@@ -5,11 +5,11 @@ export declare const TriageRequestInputSchema: z.ZodObject<{
|
|
|
5
5
|
sessionId: z.ZodOptional<z.ZodString>;
|
|
6
6
|
hint: z.ZodOptional<z.ZodObject<{
|
|
7
7
|
kind: z.ZodOptional<z.ZodEnum<{
|
|
8
|
-
idea: "idea";
|
|
9
|
-
"quick-fix": "quick-fix";
|
|
10
8
|
"bug-spec": "bug-spec";
|
|
11
|
-
"feature-spec": "feature-spec";
|
|
12
9
|
epic: "epic";
|
|
10
|
+
"feature-spec": "feature-spec";
|
|
11
|
+
idea: "idea";
|
|
12
|
+
"quick-fix": "quick-fix";
|
|
13
13
|
}>>;
|
|
14
14
|
}, z.core.$strip>>;
|
|
15
15
|
projectId: z.ZodOptional<z.ZodString>;
|
|
@@ -10,7 +10,7 @@ export interface FrontmatterSyncWarning {
|
|
|
10
10
|
specPath: string | null;
|
|
11
11
|
reason: string;
|
|
12
12
|
}
|
|
13
|
-
export declare function syncSpecFiles(updatedSpec: Spec,
|
|
13
|
+
export declare function syncSpecFiles(updatedSpec: Spec, _currentStatus: SpecStatus, newStatus: SpecStatus, projectPath?: string): Promise<{
|
|
14
14
|
warning?: FrontmatterSyncWarning;
|
|
15
15
|
}>;
|
|
16
16
|
export declare function tryReconcile(newStatus: string, specId: string, projectId: string): Promise<string | null>;
|
|
@@ -2,14 +2,15 @@ import { calculateAccuracy } from '../../engine/estimator.js';
|
|
|
2
2
|
import { metricsStore } from '../../storage/index.js';
|
|
3
3
|
import { addLesson, getLessons } from '../../storage/lessons-store.js';
|
|
4
4
|
import { dispatchFeedbackEvent } from '../learn.js';
|
|
5
|
-
import { generateProgressUpdate } from '../../engine/progress-writer.js';
|
|
6
5
|
import { autoReconcileOnDone } from '../update-status-reconcile.js';
|
|
7
6
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
8
|
-
import { replaceSectionInSpec } from '../../engine/spec-format/replace-section.js';
|
|
9
7
|
import { parseFrontmatter } from '../../engine/frontmatter-parser.js';
|
|
10
8
|
import { computeFrontmatterSha } from '../../engine/frontmatter-sha/index.js';
|
|
11
9
|
import { appendTransitionEvent } from '../../storage/transition-log.js';
|
|
12
10
|
export async function recordDoneMetrics(projectId, specId, spec, actuals, projectPath) {
|
|
11
|
+
if (actuals.devHours + actuals.reviewHours === 0) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
13
14
|
const accuracyScore = calculateAccuracy(spec.estimation, actuals.devHours + actuals.reviewHours);
|
|
14
15
|
const metricEntry = {
|
|
15
16
|
specId,
|
|
@@ -19,7 +20,7 @@ export async function recordDoneMetrics(projectId, specId, spec, actuals, projec
|
|
|
19
20
|
accuracyScore,
|
|
20
21
|
};
|
|
21
22
|
await metricsStore.addMetricEntry(projectId, metricEntry);
|
|
22
|
-
if (accuracyScore <
|
|
23
|
+
if (accuracyScore < 70) {
|
|
23
24
|
void dispatchFeedbackEvent(projectId, {
|
|
24
25
|
type: 'validate',
|
|
25
26
|
specId,
|
|
@@ -29,13 +30,13 @@ export async function recordDoneMetrics(projectId, specId, spec, actuals, projec
|
|
|
29
30
|
estimatedHours: spec.estimation.devHours + spec.estimation.reviewHours,
|
|
30
31
|
actualHours: actuals.devHours + actuals.reviewHours,
|
|
31
32
|
},
|
|
32
|
-
delta: { accuracyDrift: Math.abs(
|
|
33
|
+
delta: { accuracyDrift: Math.abs(100 - accuracyScore) },
|
|
33
34
|
timestamp: new Date().toISOString(),
|
|
34
35
|
}).catch((err) => {
|
|
35
36
|
console.error('[planu] dispatchFeedbackEvent failed:', err);
|
|
36
37
|
});
|
|
37
38
|
}
|
|
38
|
-
if (accuracyScore <
|
|
39
|
+
if (accuracyScore < 70 && projectPath) {
|
|
39
40
|
void autoCaptureLessonEstimation(spec, actuals, accuracyScore, projectId, projectPath);
|
|
40
41
|
}
|
|
41
42
|
}
|
|
@@ -51,14 +52,14 @@ async function autoCaptureLessonEstimation(spec, actuals, accuracy, projectId, p
|
|
|
51
52
|
}
|
|
52
53
|
const estimatedHours = spec.estimation.devHours + spec.estimation.reviewHours;
|
|
53
54
|
const actualHours = actuals.devHours + actuals.reviewHours;
|
|
54
|
-
const severity = accuracy <
|
|
55
|
+
const severity = accuracy < 60 ? 'high' : 'medium';
|
|
55
56
|
await addLesson(projectPath, {
|
|
56
57
|
projectId,
|
|
57
58
|
specId: spec.id,
|
|
58
59
|
category: 'estimation',
|
|
59
60
|
severity,
|
|
60
61
|
title: `Estimation miss on ${spec.title}`,
|
|
61
|
-
description: `Spec took ${String(actualHours)}h actual vs ${String(estimatedHours)}h estimated (${String(Math.round(accuracy
|
|
62
|
+
description: `Spec took ${String(actualHours)}h actual vs ${String(estimatedHours)}h estimated (${String(Math.round(accuracy))}% accuracy)`,
|
|
62
63
|
prevention: 'Review complexity and scope assumptions before estimating similar specs',
|
|
63
64
|
tags: [...spec.tags, 'auto-captured', 'estimation'],
|
|
64
65
|
});
|
|
@@ -90,7 +91,7 @@ function assertSpecBodyIntegrity(args) {
|
|
|
90
91
|
throw new Error(`SPEC_INTEGRITY_WRITE_FAILED: refusing to write ${args.specId} because update_status(${args.newStatus}) would shrink spec.md body by ${String(lost)} characters without an explicit migration marker.`);
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
|
-
export async function syncSpecFiles(updatedSpec,
|
|
94
|
+
export async function syncSpecFiles(updatedSpec, _currentStatus, newStatus, projectPath) {
|
|
94
95
|
let warning;
|
|
95
96
|
if (updatedSpec.specPath) {
|
|
96
97
|
try {
|
|
@@ -145,21 +146,6 @@ export async function syncSpecFiles(updatedSpec, currentStatus, newStatus, proje
|
|
|
145
146
|
console.warn(`[Planu] update_status: ${reason} (${updatedSpec.id})`);
|
|
146
147
|
warning = { specId: updatedSpec.id, specPath: null, reason };
|
|
147
148
|
}
|
|
148
|
-
// SPEC-1010 PR-C: Write progress into the unified spec.md ## Progress section
|
|
149
|
-
// instead of a standalone progress.md file. We do this only when specPath is
|
|
150
|
-
// known (which it always is for specs that reach syncSpecFiles).
|
|
151
|
-
if (updatedSpec.specPath) {
|
|
152
|
-
try {
|
|
153
|
-
const existing = await readFile(updatedSpec.specPath, 'utf-8');
|
|
154
|
-
// Extract current ## Progress body (may be empty for new lean specs)
|
|
155
|
-
const progressBody = extractProgressBody(existing);
|
|
156
|
-
const updatedProgress = generateProgressUpdate(updatedSpec, currentStatus, progressBody);
|
|
157
|
-
await replaceSectionInSpec(updatedSpec.specPath, 'Progress', updatedProgress);
|
|
158
|
-
}
|
|
159
|
-
catch {
|
|
160
|
-
// best-effort — never blocks the transition
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
149
|
return warning ? { warning } : {};
|
|
164
150
|
}
|
|
165
151
|
/** Flip all ` done: false` lines to ` done: true` in spec content.
|
|
@@ -169,7 +155,7 @@ function markAllCriteriaDone(content) {
|
|
|
169
155
|
}
|
|
170
156
|
/** True when content is a lean-format spec (has a `criteria:` key in the frontmatter). */
|
|
171
157
|
function isLeanSpecContent(content) {
|
|
172
|
-
return /^criteria:/m.test(content);
|
|
158
|
+
return /^criteria:[ \t]*(?:#.*)?\r?\n(?:[ \t]*\r?\n)*[ \t]+-\s+text:/m.test(content);
|
|
173
159
|
}
|
|
174
160
|
/**
|
|
175
161
|
* SPEC-630: Remove done criteria entries from lean spec and annotate the count on the criteria key.
|
|
@@ -183,27 +169,6 @@ function stripDoneCriteria(content) {
|
|
|
183
169
|
});
|
|
184
170
|
return stripped.replace(/^(criteria:)(\s*#.*)?$/m, `$1 # ${String(doneCount)} done`);
|
|
185
171
|
}
|
|
186
|
-
/**
|
|
187
|
-
* Extract the body of the `## Progress` section from spec.md content.
|
|
188
|
-
* Returns "" when the section is absent so callers get a clean empty string.
|
|
189
|
-
* Mirrors the extractSectionBody logic without importing from spec-format to
|
|
190
|
-
* keep this module's dependency chain flat.
|
|
191
|
-
*/
|
|
192
|
-
function extractProgressBody(specContent) {
|
|
193
|
-
const normalized = specContent.replace(/\r\n/g, '\n');
|
|
194
|
-
// Mask fenced code blocks so inner markdown headings are not matched
|
|
195
|
-
const masked = normalized.replace(/(```|~~~)[\s\S]*?\1/g, (block) => block.replace(/[^\n]/g, ' '));
|
|
196
|
-
const headingMatch = /^##\s+Progress\b.*$/m.exec(masked);
|
|
197
|
-
if (!headingMatch) {
|
|
198
|
-
return '';
|
|
199
|
-
}
|
|
200
|
-
const afterHeading = headingMatch.index + headingMatch[0].length;
|
|
201
|
-
const nextRe = /^##\s+\S/gm;
|
|
202
|
-
nextRe.lastIndex = afterHeading;
|
|
203
|
-
const next = nextRe.exec(masked);
|
|
204
|
-
const end = next ? next.index : normalized.length;
|
|
205
|
-
return normalized.slice(afterHeading, end).trim();
|
|
206
|
-
}
|
|
207
172
|
export async function tryReconcile(newStatus, specId, projectId) {
|
|
208
173
|
if (newStatus !== 'done') {
|
|
209
174
|
return null;
|
|
@@ -16,8 +16,6 @@ import { isLocked, getLock } from '../../storage/spec-lock-store.js';
|
|
|
16
16
|
import { runValidateGate, checkDoneGates, checkComplianceGate, checkQaGate, checkApprovedFormatGate, checkValidationReportGate, checkSpecReviewGate, writeSpecReviewArtifact, } from './dod-gates.js';
|
|
17
17
|
import { checkLifecycleEvidenceTransitionGate } from './evidence-gate.js';
|
|
18
18
|
import { buildStatusResponse, buildValidateBlockedResponse, buildDryRunResponse, } from './response-builder.js';
|
|
19
|
-
import { getSuggestedMode } from './mode-hints.js';
|
|
20
|
-
import { autoFillActuals, createDefaultActuals } from '../../engine/actuals-estimator.js';
|
|
21
19
|
import { recordDoneMetrics, syncSpecFiles, tryReconcile, recordTerminalTransitionEvent, } from './file-sync.js';
|
|
22
20
|
import { appendEntry, getLastHash } from '../../storage/audit-trail-store.js';
|
|
23
21
|
import { appendTransitionEvent } from '../../storage/transition-log.js';
|
|
@@ -117,18 +115,18 @@ function computeDoneMetrics(newStatus, spec, actuals) {
|
|
|
117
115
|
if (newStatus !== 'done' || actuals === undefined || spec === null) {
|
|
118
116
|
return null;
|
|
119
117
|
}
|
|
118
|
+
const actualHours = actuals.devHours + actuals.reviewHours;
|
|
119
|
+
if (actualHours === 0) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
120
122
|
return {
|
|
121
|
-
accuracyScore: calculateAccuracy(spec.estimation,
|
|
123
|
+
accuracyScore: calculateAccuracy(spec.estimation, actualHours),
|
|
122
124
|
estimatedHours: spec.estimation.devHours + spec.estimation.reviewHours,
|
|
123
|
-
actualHours
|
|
125
|
+
actualHours,
|
|
124
126
|
estimatedCost: spec.estimation.totalCostUsd,
|
|
125
127
|
actualCost: actuals.totalCostUsd,
|
|
126
128
|
};
|
|
127
129
|
}
|
|
128
|
-
/** Extract recordActualHint from doneActions without adding cyclomatic complexity inline. */
|
|
129
|
-
function extractRecordActualHint(doneActions) {
|
|
130
|
-
return doneActions?.recordActualHint;
|
|
131
|
-
}
|
|
132
130
|
/** SPEC-447: Build a blocked-by-compliance response. */
|
|
133
131
|
function buildBlockedByComplianceResponse(_specId, result) {
|
|
134
132
|
const scoreStr = result.score !== null ? String(result.score) : 'N/A';
|
|
@@ -269,6 +267,22 @@ export async function handleUpdateStatus(params, server) {
|
|
|
269
267
|
};
|
|
270
268
|
}
|
|
271
269
|
const originalStatus = spec.status;
|
|
270
|
+
if (newStatus === 'done' && actuals === undefined) {
|
|
271
|
+
return {
|
|
272
|
+
content: [
|
|
273
|
+
{
|
|
274
|
+
type: 'text',
|
|
275
|
+
text: 'actuals_required: provide measured values for done. Use zero only when a metric is explicitly unavailable; Planu does not estimate provider tokens or costs.',
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
isError: true,
|
|
279
|
+
structuredContent: {
|
|
280
|
+
error: 'actuals_required',
|
|
281
|
+
code: 422,
|
|
282
|
+
fixHint: 'Provide actuals with measured values or zero for unavailable metrics. Values are preserved without estimation.',
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
}
|
|
272
286
|
// SPEC-301: Reject status changes when spec is locked by another holder (legacy manual lock)
|
|
273
287
|
const lockError = await checkSpecLockGate(params.projectPath, specId);
|
|
274
288
|
if (lockError) {
|
|
@@ -477,7 +491,7 @@ export async function handleUpdateStatus(params, server) {
|
|
|
477
491
|
let forcedBypassWarning = null;
|
|
478
492
|
const [codeRealityResult, doneGatesResult] = await Promise.all([
|
|
479
493
|
// Code reality: only relevant for 'implementing'
|
|
480
|
-
newStatus === 'implementing' && effectiveGatePath
|
|
494
|
+
newStatus === 'implementing' && !reverseTransition && effectiveGatePath
|
|
481
495
|
? checkCodeReality(effectiveGatePath, spec.title).catch(() => null)
|
|
482
496
|
: Promise.resolve(null),
|
|
483
497
|
// Done gates: only relevant for 'done'
|
|
@@ -521,8 +535,8 @@ export async function handleUpdateStatus(params, server) {
|
|
|
521
535
|
newStatus === 'done' && effectiveGatePath && !crashShieldSkipReason
|
|
522
536
|
? withToolTimeout(scanCrashRisks(effectiveGatePath).catch(() => null), 9_000, null)
|
|
523
537
|
: Promise.resolve(null),
|
|
524
|
-
//
|
|
525
|
-
newStatus === '
|
|
538
|
+
// The heuristic scorer is a review aid. Done relies on the authoritative validate report.
|
|
539
|
+
newStatus === 'review'
|
|
526
540
|
? withToolTimeout(checkComplianceGate(specId, projectId, effectiveGatePath), 9_000, {
|
|
527
541
|
skipped: true,
|
|
528
542
|
blocked: false,
|
|
@@ -631,18 +645,8 @@ export async function handleUpdateStatus(params, server) {
|
|
|
631
645
|
blockingReasons: [],
|
|
632
646
|
});
|
|
633
647
|
}
|
|
634
|
-
//
|
|
635
|
-
|
|
636
|
-
if (newStatus === 'done') {
|
|
637
|
-
if (!resolvedActuals) {
|
|
638
|
-
// Agent did not pass actuals at all — create from spec estimation
|
|
639
|
-
resolvedActuals = createDefaultActuals(spec.estimation);
|
|
640
|
-
}
|
|
641
|
-
else {
|
|
642
|
-
// Agent passed actuals but tokens/costs are zero — auto-fill
|
|
643
|
-
resolvedActuals = autoFillActuals(resolvedActuals, spec.estimation);
|
|
644
|
-
}
|
|
645
|
-
}
|
|
648
|
+
// Explicit actuals are preserved verbatim. Provider usage and cost are never inferred.
|
|
649
|
+
const resolvedActuals = actuals;
|
|
646
650
|
// Run transition-specific actions
|
|
647
651
|
const implActions = newStatus === 'implementing' ? await runImplementingActions(projectId, specId) : null;
|
|
648
652
|
const doneActions = newStatus === 'done' ? await runDoneActions(projectId, specId, spec.gitBranch) : null;
|
|
@@ -1030,7 +1034,6 @@ export async function handleUpdateStatus(params, server) {
|
|
|
1030
1034
|
if (cascadeResult.affectedSpecs.length > 0) {
|
|
1031
1035
|
collector.pushOk('cascade', `Notified ${String(cascadeResult.affectedSpecs.length)} downstream spec(s)`);
|
|
1032
1036
|
}
|
|
1033
|
-
const modeSuggestion = getSuggestedMode(newStatus);
|
|
1034
1037
|
const result = compactObj({
|
|
1035
1038
|
specId,
|
|
1036
1039
|
projectId,
|
|
@@ -1068,8 +1071,6 @@ export async function handleUpdateStatus(params, server) {
|
|
|
1068
1071
|
metrics: doneMetrics,
|
|
1069
1072
|
complianceGateResult,
|
|
1070
1073
|
versionSnapshotTag,
|
|
1071
|
-
suggestedClaudeMode: modeSuggestion?.mode ?? null,
|
|
1072
|
-
modeHint: modeSuggestion?.hint ?? null,
|
|
1073
1074
|
orchestrationPlan,
|
|
1074
1075
|
// SPEC-728: DepGuard result (null when gate was not applicable)
|
|
1075
1076
|
depGuard: depGuardResult,
|
|
@@ -1105,7 +1106,7 @@ export async function handleUpdateStatus(params, server) {
|
|
|
1105
1106
|
const reconciliationMarkdown = await tryReconcile(newStatus, specId, projectId);
|
|
1106
1107
|
// SPEC-754: Append shell-hygiene reminder when transitioning to done on claude-code host
|
|
1107
1108
|
const shellHygieneHint = newStatus === 'done' && detectHost() === 'claude-code' ? shellHygieneReminder() : null;
|
|
1108
|
-
return buildStatusResponse(result, specId, currentStatus, newStatus, reconciliationMarkdown,
|
|
1109
|
+
return buildStatusResponse(result, specId, currentStatus, newStatus, reconciliationMarkdown, spec.title, shellHygieneHint);
|
|
1109
1110
|
}
|
|
1110
1111
|
finally {
|
|
1111
1112
|
// SPEC-719: Release cross-process lock in all exit paths (return, throw)
|
|
@@ -13,5 +13,5 @@ export declare function buildValidateBlockedResponse(specId: string, result: Ext
|
|
|
13
13
|
*/
|
|
14
14
|
export declare function buildDryRunResponse(dryRunResult: DryRunResult): ToolResult;
|
|
15
15
|
export declare const STATUS_EMOJIS: Record<SpecStatus, string>;
|
|
16
|
-
export declare function buildStatusResponse(result: Record<string, unknown>, specId: string, currentStatus: SpecStatus, newStatus: SpecStatus, reconciliationMarkdown?: string | null,
|
|
16
|
+
export declare function buildStatusResponse(result: Record<string, unknown>, specId: string, currentStatus: SpecStatus, newStatus: SpecStatus, reconciliationMarkdown?: string | null, specTitle?: string, shellHygieneHint?: string | null): ToolResult;
|
|
17
17
|
//# sourceMappingURL=response-builder.d.ts.map
|
|
@@ -79,7 +79,7 @@ export const STATUS_EMOJIS = {
|
|
|
79
79
|
discarded: '🗑️',
|
|
80
80
|
};
|
|
81
81
|
// eslint-disable-next-line complexity -- aggregates many host- and status-specific branches
|
|
82
|
-
export function buildStatusResponse(result, specId, currentStatus, newStatus, reconciliationMarkdown,
|
|
82
|
+
export function buildStatusResponse(result, specId, currentStatus, newStatus, reconciliationMarkdown, specTitle, shellHygieneHint) {
|
|
83
83
|
const fromEmoji = STATUS_EMOJIS[currentStatus];
|
|
84
84
|
const toEmoji = STATUS_EMOJIS[newStatus];
|
|
85
85
|
const r = result;
|
|
@@ -102,18 +102,15 @@ export function buildStatusResponse(result, specId, currentStatus, newStatus, re
|
|
|
102
102
|
lines.push(`\n🔗 ${r.prSuggestion.title}`);
|
|
103
103
|
}
|
|
104
104
|
if (r.metrics) {
|
|
105
|
-
lines.push(`\n📊 Accuracy: ${Math.round(r.metrics.accuracyScore
|
|
105
|
+
lines.push(`\n📊 Accuracy: ${Math.round(r.metrics.accuracyScore)}% | ` +
|
|
106
106
|
`Estimated: ${String(r.metrics.estimatedHours)}h → Actual: ${String(r.metrics.actualHours)}h`);
|
|
107
107
|
}
|
|
108
108
|
if (r.actuals && (r.actuals.tokensOpus > 0 || r.actuals.tokensSonnet > 0)) {
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
const sonnet = r.actuals.tokensSonnet > 0
|
|
112
|
-
? `${prefix}${r.actuals.tokensSonnet.toLocaleString()} sonnet`
|
|
113
|
-
: null;
|
|
109
|
+
const opus = r.actuals.tokensOpus > 0 ? `${r.actuals.tokensOpus.toLocaleString()} opus` : null;
|
|
110
|
+
const sonnet = r.actuals.tokensSonnet > 0 ? `${r.actuals.tokensSonnet.toLocaleString()} sonnet` : null;
|
|
114
111
|
const tokenStr = [opus, sonnet].filter(Boolean).join(' + ');
|
|
115
|
-
const costStr = r.actuals.apiCostUsd > 0 ? ` |
|
|
116
|
-
lines.push(`\n🤖 Tokens: ${tokenStr}${costStr}
|
|
112
|
+
const costStr = r.actuals.apiCostUsd > 0 ? ` | $${r.actuals.apiCostUsd.toFixed(3)} API` : '';
|
|
113
|
+
lines.push(`\n🤖 Tokens: ${tokenStr}${costStr}`);
|
|
117
114
|
}
|
|
118
115
|
if (r.validateScore !== null && r.validateScore !== undefined) {
|
|
119
116
|
const sourceSuffix = r.validateScoreSource === 'forced-best-effort-validateSpec'
|
|
@@ -180,22 +177,17 @@ export function buildStatusResponse(result, specId, currentStatus, newStatus, re
|
|
|
180
177
|
const v = r.orchestrationPlan.parallelSafetyVerdict ? 'parallel-safe' : 'sequential-required';
|
|
181
178
|
lines.push(`\n🤖 Orchestration: ${String(r.orchestrationPlan.executionOrder.length)} waves | ${v}`);
|
|
182
179
|
}
|
|
183
|
-
// SPEC-494: Claude Code mode hint
|
|
184
|
-
if (r.suggestedClaudeMode) {
|
|
185
|
-
lines.push(`\n💡 **Suggested Claude mode:** \`${r.suggestedClaudeMode}\` — ${r.modeHint ?? ''}`);
|
|
186
|
-
}
|
|
187
|
-
if (recordActualHint) {
|
|
188
|
-
lines.push(`\n💡 ${recordActualHint}`);
|
|
189
|
-
}
|
|
190
180
|
// SPEC-754: Shell hygiene reminder for claude-code host on done transition
|
|
191
181
|
if (shellHygieneHint) {
|
|
192
182
|
lines.push(`\n🐚 **Shell hygiene:** ${shellHygieneHint}`);
|
|
193
183
|
}
|
|
194
184
|
// SPEC-340: nextAction and lifecycleMap
|
|
195
|
-
const nextAction = buildNextAction(newStatus);
|
|
196
185
|
const lifecycleMap = buildLifecycleMap(newStatus);
|
|
186
|
+
const nextAction = lifecycleMap.next === null ? null : buildNextAction(newStatus);
|
|
197
187
|
lines.push(buildLifecycleMapLine(lifecycleMap));
|
|
198
|
-
|
|
188
|
+
if (nextAction) {
|
|
189
|
+
lines.push(`\n**Next step:** ${nextAction.step}\n> ${nextAction.prompt}\n> \`${nextAction.suggestedCommand}\` — ${nextAction.whenToDoIt}`);
|
|
190
|
+
}
|
|
199
191
|
const allNextSteps = [...(r.implementingSuggestions ?? [])];
|
|
200
192
|
const baseText = formatSuccess(t('tools.response.statusTitle'), lines.join(''));
|
|
201
193
|
const withNextSteps = allNextSteps.length > 0 ? addNextSteps(baseText, allNextSteps) : baseText;
|
|
@@ -284,7 +276,7 @@ export function buildStatusResponse(result, specId, currentStatus, newStatus, re
|
|
|
284
276
|
warningsTop5: warningItems.slice(0, 5),
|
|
285
277
|
affectedSpecsCount: r.cascade.affectedSpecs.length,
|
|
286
278
|
affectedSpecsTop5: r.cascade.affectedSpecs.slice(0, 5),
|
|
287
|
-
nextAction,
|
|
279
|
+
nextAction: nextAction ?? undefined,
|
|
288
280
|
lifecycleMap,
|
|
289
281
|
humanSummary,
|
|
290
282
|
orchestrationPlan: r.orchestrationPlan
|
|
@@ -115,7 +115,7 @@ export function checkTransition(currentStatus, newStatus) {
|
|
|
115
115
|
* Returns an error ToolResult if blocked, null if passed.
|
|
116
116
|
*/
|
|
117
117
|
export function checkDorGate(spec, specId, projectId, newStatus) {
|
|
118
|
-
if (newStatus !== 'implementing') {
|
|
118
|
+
if (newStatus !== 'implementing' || isReverseTransition(spec.status, newStatus)) {
|
|
119
119
|
return null;
|
|
120
120
|
}
|
|
121
121
|
const dorResult = validateDoR(spec);
|