@planu/cli 4.3.2 → 4.3.4
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/engine/spec-migrator/planu-canonical-policy.js +7 -0
- package/dist/hosts/codex/config-scaffold.js +11 -0
- package/dist/tools/register-spec-tools/core-spec-tools.js +43 -0
- package/dist/tools/update-status/file-sync.js +29 -0
- package/dist/types/spec-format.d.ts +1 -0
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
## [4.3.4] - 2026-05-22
|
|
2
|
+
|
|
3
|
+
**Tarball SHA-256:** `e1ac042fd623c1421d7a0788fed14c369472489180ffe80eb8bce4d422d360d8`
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
- fix(planu): keep host adapters outside managed state
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## [4.3.3] - 2026-05-22
|
|
10
|
+
|
|
11
|
+
**Tarball SHA-256:** `d681d7d176a263f3b059c4ed5fbc7647a17758dc3c56cc79fd47658bab082fe6`
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
- fix(planu): expose update_status routing evidence
|
|
15
|
+
|
|
16
|
+
|
|
1
17
|
## [4.3.2] - 2026-05-22
|
|
2
18
|
|
|
3
19
|
**Tarball SHA-256:** `7732de964d5e10d24bdab5ea79baee3d281654cf5caff1f713671502c8e09ee3`
|
|
@@ -4,6 +4,7 @@ export const PLANU_CANONICAL_POLICY = {
|
|
|
4
4
|
canonicalRootFiles: ['conventions.json', 'context.md', 'session-context.md', 'session.json'],
|
|
5
5
|
canonicalRootDirs: ['releases', 'specs'],
|
|
6
6
|
canonicalSpecFiles: ['spec.md'],
|
|
7
|
+
forbiddenHostAssetRootDirs: ['agents', 'skills', 'rules', 'hooks'],
|
|
7
8
|
generatedRuntimePatterns: [
|
|
8
9
|
'planu/index.html',
|
|
9
10
|
'planu/roadmap.html',
|
|
@@ -57,6 +58,12 @@ export function canonicalContractText() {
|
|
|
57
58
|
' specs/',
|
|
58
59
|
' SPEC-XXX-slug/',
|
|
59
60
|
' spec.md',
|
|
61
|
+
'',
|
|
62
|
+
'Host adapters are written outside planu/:',
|
|
63
|
+
' Claude Code: .claude/agents, .claude/skills, .claude/rules',
|
|
64
|
+
' Codex: AGENTS.md, .agents/skills, .codex/agents',
|
|
65
|
+
' Gemini: GEMINI.md, .gemini/skills',
|
|
66
|
+
' Cursor: .cursor/rules/planu.mdc',
|
|
60
67
|
].join('\n');
|
|
61
68
|
}
|
|
62
69
|
//# sourceMappingURL=planu-canonical-policy.js.map
|
|
@@ -13,6 +13,7 @@ import { join } from 'node:path';
|
|
|
13
13
|
import { detectCodexWorkspace } from './workspace-scope.js';
|
|
14
14
|
import { buildRulesForHost } from '../../engine/host-rules-templates/index.js';
|
|
15
15
|
import { assertEnglishOnlyArtifactText } from '../../engine/spec-language/english-only.js';
|
|
16
|
+
import { generateImplementerRole, generateReviewerRole, generateSpecWriterRole, } from '../../engine/ai-integration/codex/agent-roles-generator.js';
|
|
16
17
|
const CODEX_CONFIG_TOML = `# .openai/config.toml — Planu Codex workspace configuration
|
|
17
18
|
# Auto-generated by Planu init_project. Safe to customize.
|
|
18
19
|
|
|
@@ -69,6 +70,7 @@ export async function scaffoldCodexConfig(projectPath, planuVersion = '1.96.0')
|
|
|
69
70
|
await mkdir(openaiDir, { recursive: true });
|
|
70
71
|
await writeIfMissing(join(openaiDir, 'config.toml'), CODEX_CONFIG_TOML);
|
|
71
72
|
await writeIfMissing(join(projectPath, 'AGENTS.md'), AGENTS_MD_CONTENT);
|
|
73
|
+
await scaffoldCodexAgentRoles(projectPath);
|
|
72
74
|
await injectRulesBlock(join(projectPath, 'AGENTS.md'), planuVersion);
|
|
73
75
|
}
|
|
74
76
|
// ---------------------------------------------------------------------------
|
|
@@ -86,6 +88,15 @@ async function writeIfMissing(filePath, content) {
|
|
|
86
88
|
await writeFile(filePath, content, 'utf-8');
|
|
87
89
|
}
|
|
88
90
|
}
|
|
91
|
+
async function scaffoldCodexAgentRoles(projectPath) {
|
|
92
|
+
const agentsDir = join(projectPath, '.codex', 'agents');
|
|
93
|
+
await mkdir(agentsDir, { recursive: true });
|
|
94
|
+
await Promise.all([
|
|
95
|
+
writeIfMissing(join(agentsDir, 'spec-writer.toml'), generateSpecWriterRole()),
|
|
96
|
+
writeIfMissing(join(agentsDir, 'implementer.toml'), generateImplementerRole()),
|
|
97
|
+
writeIfMissing(join(agentsDir, 'reviewer.toml'), generateReviewerRole()),
|
|
98
|
+
]);
|
|
99
|
+
}
|
|
89
100
|
const PLANU_RULES_BLOCK_REGEX = /<!-- planu:rules:start[\s\S]*?<!-- planu:rules:end -->/;
|
|
90
101
|
const PLANU_RULES_START_REGEX = /<!-- planu:rules:start[^>]* -->/;
|
|
91
102
|
/**
|
|
@@ -286,6 +286,49 @@ export function registerCoreSpecTools(server) {
|
|
|
286
286
|
.optional()
|
|
287
287
|
.describe('Absolute path to the project root. Used to derive projectId automatically.'),
|
|
288
288
|
status: SpecStatusEnum.describe('New status'),
|
|
289
|
+
sessionId: z
|
|
290
|
+
.string()
|
|
291
|
+
.max(500)
|
|
292
|
+
.optional()
|
|
293
|
+
.describe('Agent/session ID performing this lifecycle transition.'),
|
|
294
|
+
modelId: z
|
|
295
|
+
.string()
|
|
296
|
+
.max(500)
|
|
297
|
+
.optional()
|
|
298
|
+
.describe('Concrete model ID used for this transition. Can satisfy SDD model-routing evidence when it maps to the required tier.'),
|
|
299
|
+
modelTierUsed: z
|
|
300
|
+
.enum(['max', 'implementation', 'review'])
|
|
301
|
+
.optional()
|
|
302
|
+
.describe('SDD model tier evidence for this phase: max for approval/arbitration, implementation for coding, review for reviewer work.'),
|
|
303
|
+
contextHash: z
|
|
304
|
+
.string()
|
|
305
|
+
.max(500)
|
|
306
|
+
.optional()
|
|
307
|
+
.describe('SHA-256 hash of the persisted context package used to prove context continuity across agents.'),
|
|
308
|
+
handoffPath: z
|
|
309
|
+
.string()
|
|
310
|
+
.max(4096)
|
|
311
|
+
.optional()
|
|
312
|
+
.describe('Path to the persisted handoff package generated by package_handoff. Required for implementing/done unless handoffArtifactId is provided.'),
|
|
313
|
+
handoffArtifactId: z
|
|
314
|
+
.string()
|
|
315
|
+
.max(500)
|
|
316
|
+
.optional()
|
|
317
|
+
.describe('External handoff artifact ID when the host stores handoff evidence outside the filesystem.'),
|
|
318
|
+
reviewedBy: z
|
|
319
|
+
.string()
|
|
320
|
+
.max(500)
|
|
321
|
+
.optional()
|
|
322
|
+
.describe('Reviewer identity/evidence required before status=done.'),
|
|
323
|
+
arbitratedBy: z
|
|
324
|
+
.string()
|
|
325
|
+
.max(500)
|
|
326
|
+
.optional()
|
|
327
|
+
.describe('Arbiter identity/evidence required before status=done.'),
|
|
328
|
+
reconcileRequired: z
|
|
329
|
+
.boolean()
|
|
330
|
+
.optional()
|
|
331
|
+
.describe('Set true when implementation drift requires reconcile_spec before status=done; true blocks done.'),
|
|
289
332
|
actuals: z
|
|
290
333
|
.object({
|
|
291
334
|
devHours: z.number().min(0),
|
|
@@ -67,6 +67,29 @@ async function autoCaptureLessonEstimation(spec, actuals, accuracy, projectId, p
|
|
|
67
67
|
/* best-effort — never throw */
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
+
const SPEC_BODY_SHRINK_ABSOLUTE_THRESHOLD = 200;
|
|
71
|
+
const SPEC_BODY_SHRINK_RATIO_THRESHOLD = 0.05;
|
|
72
|
+
function bodyWithoutFrontmatter(content) {
|
|
73
|
+
return parseFrontmatter(content).body;
|
|
74
|
+
}
|
|
75
|
+
function hasExplicitTruncationMarker(content) {
|
|
76
|
+
return /\btruncated\b|SPEC_INTEGRITY_ALLOWED_REDUCTION/i.test(content);
|
|
77
|
+
}
|
|
78
|
+
function assertSpecBodyIntegrity(args) {
|
|
79
|
+
if (args.newStatus === 'done') {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (hasExplicitTruncationMarker(args.updated)) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const originalBodyLength = bodyWithoutFrontmatter(args.original).length;
|
|
86
|
+
const updatedBodyLength = bodyWithoutFrontmatter(args.updated).length;
|
|
87
|
+
const lost = originalBodyLength - updatedBodyLength;
|
|
88
|
+
const ratio = originalBodyLength === 0 ? 0 : lost / originalBodyLength;
|
|
89
|
+
if (lost > SPEC_BODY_SHRINK_ABSOLUTE_THRESHOLD && ratio > SPEC_BODY_SHRINK_RATIO_THRESHOLD) {
|
|
90
|
+
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
|
+
}
|
|
70
93
|
export async function syncSpecFiles(updatedSpec, currentStatus, newStatus, projectPath) {
|
|
71
94
|
let warning;
|
|
72
95
|
if (updatedSpec.specPath) {
|
|
@@ -80,6 +103,12 @@ export async function syncSpecFiles(updatedSpec, currentStatus, newStatus, proje
|
|
|
80
103
|
updatedContent = stripDoneCriteria(updatedContent);
|
|
81
104
|
}
|
|
82
105
|
}
|
|
106
|
+
assertSpecBodyIntegrity({
|
|
107
|
+
original: specContent,
|
|
108
|
+
updated: updatedContent,
|
|
109
|
+
newStatus,
|
|
110
|
+
specId: updatedSpec.id,
|
|
111
|
+
});
|
|
83
112
|
await writeFile(updatedSpec.specPath, updatedContent, 'utf-8');
|
|
84
113
|
// SPEC-544: Auto-stage spec.md after writing so it's included in the user's next commit
|
|
85
114
|
// SPEC-575: Auto-commit staged planu/ docs (idempotent, safe-fail)
|
|
@@ -47,6 +47,7 @@ export interface PlanuCanonicalPathPolicy {
|
|
|
47
47
|
readonly canonicalRootFiles: readonly string[];
|
|
48
48
|
readonly canonicalRootDirs: readonly string[];
|
|
49
49
|
readonly canonicalSpecFiles: readonly string[];
|
|
50
|
+
readonly forbiddenHostAssetRootDirs: readonly string[];
|
|
50
51
|
readonly generatedRuntimePatterns: readonly string[];
|
|
51
52
|
readonly legacyMergeBeforeDeleteFiles: readonly string[];
|
|
52
53
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planu/cli",
|
|
3
|
-
"version": "4.3.
|
|
3
|
+
"version": "4.3.4",
|
|
4
4
|
"description": "Planu — MCP Server for Spec Driven Development with native Rust acceleration for hot paths. Cross-platform (Linux/macOS/Windows, x64/arm64, glibc/musl).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -32,14 +32,14 @@
|
|
|
32
32
|
"packageName": "@planu/core"
|
|
33
33
|
},
|
|
34
34
|
"optionalDependencies": {
|
|
35
|
-
"@planu/core-darwin-arm64": "4.3.
|
|
36
|
-
"@planu/core-darwin-x64": "4.3.
|
|
37
|
-
"@planu/core-linux-arm64-gnu": "4.3.
|
|
38
|
-
"@planu/core-linux-arm64-musl": "4.3.
|
|
39
|
-
"@planu/core-linux-x64-gnu": "4.3.
|
|
40
|
-
"@planu/core-linux-x64-musl": "4.3.
|
|
41
|
-
"@planu/core-win32-arm64-msvc": "4.3.
|
|
42
|
-
"@planu/core-win32-x64-msvc": "4.3.
|
|
35
|
+
"@planu/core-darwin-arm64": "4.3.4",
|
|
36
|
+
"@planu/core-darwin-x64": "4.3.4",
|
|
37
|
+
"@planu/core-linux-arm64-gnu": "4.3.4",
|
|
38
|
+
"@planu/core-linux-arm64-musl": "4.3.4",
|
|
39
|
+
"@planu/core-linux-x64-gnu": "4.3.4",
|
|
40
|
+
"@planu/core-linux-x64-musl": "4.3.4",
|
|
41
|
+
"@planu/core-win32-arm64-msvc": "4.3.4",
|
|
42
|
+
"@planu/core-win32-x64-msvc": "4.3.4"
|
|
43
43
|
},
|
|
44
44
|
"engines": {
|
|
45
45
|
"node": ">=24.0.0"
|