@planu/cli 3.9.6 → 3.9.8
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/dist/config/skill-templates/planu-context-assets.md +94 -0
- package/dist/engine/handoff-packager.js +151 -4
- package/dist/engine/sdd-model-routing.d.ts +16 -0
- package/dist/engine/sdd-model-routing.js +195 -0
- package/dist/engine/universal-rules/catalog.js +10 -0
- package/dist/engine/universal-rules/installer.js +9 -3
- package/dist/engine/universal-rules/rules/planu-approval-gates.d.ts +3 -0
- package/dist/engine/universal-rules/rules/planu-approval-gates.js +41 -0
- package/dist/engine/universal-rules/rules/planu-bdd-criteria.d.ts +3 -0
- package/dist/engine/universal-rules/rules/planu-bdd-criteria.js +45 -0
- package/dist/engine/universal-rules/rules/planu-english-specs.d.ts +3 -0
- package/dist/engine/universal-rules/rules/planu-english-specs.js +36 -0
- package/dist/engine/universal-rules/rules/planu-release-policy.d.ts +3 -0
- package/dist/engine/universal-rules/rules/planu-release-policy.js +38 -0
- package/dist/engine/universal-rules/rules/planu-sdd-model-routing.d.ts +3 -0
- package/dist/engine/universal-rules/rules/planu-sdd-model-routing.js +51 -0
- package/dist/tools/init-project/host-assets-writer.d.ts +21 -0
- package/dist/tools/init-project/host-assets-writer.js +171 -0
- package/dist/tools/init-project/scaffold-writer.d.ts +8 -0
- package/dist/tools/init-project/scaffold-writer.js +122 -74
- package/dist/tools/package-handoff.js +76 -0
- package/dist/tools/update-status/file-sync.d.ts +1 -0
- package/dist/tools/update-status/file-sync.js +1 -0
- package/dist/tools/update-status/index.js +88 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/readiness.d.ts +10 -1
- package/dist/types/sdd-model-routing.d.ts +22 -0
- package/dist/types/sdd-model-routing.js +2 -0
- package/dist/types/spec/inputs.d.ts +23 -0
- package/package.json +7 -7
- package/dist/types/data/estimation.d.ts +0 -147
- package/dist/types/data/estimation.js +0 -2
- package/dist/types/data/index.d.ts +0 -5
- package/dist/types/data/index.js +0 -6
- package/dist/types/data/velocity.d.ts +0 -168
- package/dist/types/data/velocity.js +0 -4
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// engine/universal-rules/rules/planu-english-specs.ts — Universal rule: specs are written in English
|
|
2
|
+
function buildBody() {
|
|
3
|
+
return `# Planu Specs Must Be Written in English
|
|
4
|
+
|
|
5
|
+
Auto-generated by \`init_project\`. Do not edit manually.
|
|
6
|
+
|
|
7
|
+
## Rule
|
|
8
|
+
|
|
9
|
+
All generated spec artifacts are written in English, regardless of the user's conversation language:
|
|
10
|
+
|
|
11
|
+
- \`spec.md\`
|
|
12
|
+
- \`technical.md\`
|
|
13
|
+
- architecture notes inside \`planu/specs/**\`
|
|
14
|
+
- acceptance criteria
|
|
15
|
+
- implementation notes
|
|
16
|
+
- validation and reconciliation notes
|
|
17
|
+
|
|
18
|
+
User-facing chat may use the user's preferred language. The spec contract itself stays in English so every agent, tool, and CI gate can parse it consistently.
|
|
19
|
+
|
|
20
|
+
## Hard Blocks
|
|
21
|
+
|
|
22
|
+
- Do not create mixed-language acceptance criteria.
|
|
23
|
+
- Do not translate BDD keywords.
|
|
24
|
+
- Do not approve a spec that contains unresolved placeholders such as \`to be determined\`, \`TBD\`, \`TODO\`, or equivalent filler.
|
|
25
|
+
`;
|
|
26
|
+
}
|
|
27
|
+
export const planuEnglishSpecsRule = {
|
|
28
|
+
id: 'planu-english-specs',
|
|
29
|
+
name: 'Planu English Specs',
|
|
30
|
+
description: 'Requires Planu spec artifacts to be written in English.',
|
|
31
|
+
category: 'quality',
|
|
32
|
+
applicableHosts: ['all'],
|
|
33
|
+
defaultEnabled: true,
|
|
34
|
+
buildContent: (_host) => buildBody(),
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=planu-english-specs.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// engine/universal-rules/rules/planu-release-policy.ts — Universal rule: release decision
|
|
2
|
+
function buildBody() {
|
|
3
|
+
return `# Planu Release Policy
|
|
4
|
+
|
|
5
|
+
Auto-generated by \`init_project\`. Do not edit manually.
|
|
6
|
+
|
|
7
|
+
## Rule
|
|
8
|
+
|
|
9
|
+
When implementation changes are complete and validated, always address release explicitly:
|
|
10
|
+
|
|
11
|
+
- create the release when the user has already requested shipping or publishing
|
|
12
|
+
- otherwise ask before publishing, tagging, or pushing externally visible release artifacts
|
|
13
|
+
|
|
14
|
+
## Required Release Checklist
|
|
15
|
+
|
|
16
|
+
- tests and validation commands passed
|
|
17
|
+
- changelog or release notes updated when applicable
|
|
18
|
+
- package version and git tag stay aligned
|
|
19
|
+
- main/develop branch state is reconciled when the repository uses both
|
|
20
|
+
- published package smoke check is run after release when applicable
|
|
21
|
+
|
|
22
|
+
## Hard Blocks
|
|
23
|
+
|
|
24
|
+
- Do not silently skip release discussion after completed product/runtime changes.
|
|
25
|
+
- Do not publish without validation evidence.
|
|
26
|
+
- Do not leave release notes promising capabilities that are not in the public runtime.
|
|
27
|
+
`;
|
|
28
|
+
}
|
|
29
|
+
export const planuReleasePolicyRule = {
|
|
30
|
+
id: 'planu-release-policy',
|
|
31
|
+
name: 'Planu Release Policy',
|
|
32
|
+
description: 'Requires an explicit release decision after validated implementation work.',
|
|
33
|
+
category: 'safety',
|
|
34
|
+
applicableHosts: ['all'],
|
|
35
|
+
defaultEnabled: true,
|
|
36
|
+
buildContent: (_host) => buildBody(),
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=planu-release-policy.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// engine/universal-rules/rules/planu-sdd-model-routing.ts — Universal rule: model routing + context continuity
|
|
2
|
+
function buildBody() {
|
|
3
|
+
return `# Planu SDD Model Routing and Context Guarantee
|
|
4
|
+
|
|
5
|
+
Auto-generated by \`init_project\`. Do not edit manually.
|
|
6
|
+
|
|
7
|
+
## Required Model Tiers
|
|
8
|
+
|
|
9
|
+
- Analysis/spec phase (\`facilitate\`, \`create_spec\`, \`challenge_spec\`, \`check_readiness\`): use the strongest available model, such as Opus, GPT-5.5, Gemini Pro/Ultra, or equivalent.
|
|
10
|
+
- Implementation phase: use a lower/intermediate implementation model only after the approved spec and handoff package are complete.
|
|
11
|
+
- Review phase: use a reviewer-grade model/agent.
|
|
12
|
+
- Arbitration/close phase: use the strongest available model or an explicit arbiter.
|
|
13
|
+
|
|
14
|
+
## Phase Gates
|
|
15
|
+
|
|
16
|
+
- \`update_status(approved)\` must include max-model evidence: \`modelTierUsed=max\` or a frontier \`modelId\`.
|
|
17
|
+
- \`update_status(implementing)\` must include \`modelTierUsed=implementation\`, \`contextHash\`, and \`handoffPath\` or \`handoffArtifactId\`.
|
|
18
|
+
- \`update_status(done)\` must include validate evidence, \`reviewedBy\`, \`arbitratedBy\`, \`contextHash\`, and the handoff reference.
|
|
19
|
+
- If implementation changes architecture or scope, move the spec back to \`review\` and run \`reconcile_spec\`.
|
|
20
|
+
|
|
21
|
+
## Context Contract
|
|
22
|
+
|
|
23
|
+
\`package_handoff\` is the operating contract for the implementer. It must include:
|
|
24
|
+
|
|
25
|
+
- objective
|
|
26
|
+
- BDD acceptance criteria
|
|
27
|
+
- files to modify/create
|
|
28
|
+
- ownership
|
|
29
|
+
- test plan
|
|
30
|
+
- risks
|
|
31
|
+
- out-of-scope
|
|
32
|
+
- current state
|
|
33
|
+
- next action
|
|
34
|
+
|
|
35
|
+
The package must be persisted outside chat history. Do not rely on memory from the current conversation.
|
|
36
|
+
|
|
37
|
+
## Force Policy
|
|
38
|
+
|
|
39
|
+
Forcing a blocked phase is allowed only with an audited reason of at least 100 characters. The reason must state which gate was bypassed and what follow-up is required.
|
|
40
|
+
`;
|
|
41
|
+
}
|
|
42
|
+
export const planuSddModelRoutingRule = {
|
|
43
|
+
id: 'planu-sdd-model-routing',
|
|
44
|
+
name: 'Planu SDD Model Routing',
|
|
45
|
+
description: 'Requires correct model tiers and reconstructible context across SDD phases.',
|
|
46
|
+
category: 'safety',
|
|
47
|
+
applicableHosts: ['all'],
|
|
48
|
+
defaultEnabled: true,
|
|
49
|
+
buildContent: (_host) => buildBody(),
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=planu-sdd-model-routing.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ProjectKnowledge } from '../../types/index.js';
|
|
2
|
+
import type { HostId, InstalledRule } from '../../types/universal-rules/index.js';
|
|
3
|
+
export type InitAssetHost = HostId | 'opencode';
|
|
4
|
+
export interface InitHostAssetsResult {
|
|
5
|
+
detectedHosts: InitAssetHost[];
|
|
6
|
+
rulesInstalled: InstalledRule[];
|
|
7
|
+
skillsInstalled: {
|
|
8
|
+
host: HostId;
|
|
9
|
+
name: string;
|
|
10
|
+
path: string;
|
|
11
|
+
}[];
|
|
12
|
+
opencodeConfigured: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Detect every project host that should receive Planu-owned assets.
|
|
16
|
+
* Environment identifies the active MCP host; files identify other checked-in
|
|
17
|
+
* host configs that should stay in sync too.
|
|
18
|
+
*/
|
|
19
|
+
export declare function detectInitAssetHosts(projectPath: string): Promise<InitAssetHost[]>;
|
|
20
|
+
export declare function installInitHostAssets(projectPath: string, _knowledge: ProjectKnowledge): Promise<InitHostAssetsResult>;
|
|
21
|
+
//# sourceMappingURL=host-assets-writer.d.ts.map
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// tools/init-project/host-assets-writer.ts — Host-aware rules/skills installed by init_project
|
|
2
|
+
import { access, readFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { detectHost } from '../../engine/host-detection/detect-host.js';
|
|
5
|
+
import { handleCreateSkill } from '../create-skill.js';
|
|
6
|
+
const CORE_SKILL_TEMPLATES = [
|
|
7
|
+
'planu-new-spec.md',
|
|
8
|
+
'planu-validate.md',
|
|
9
|
+
'planu-release.md',
|
|
10
|
+
'planu-resume-work.md',
|
|
11
|
+
'planu-multi-teammate-review.md',
|
|
12
|
+
'planu-context-assets.md',
|
|
13
|
+
];
|
|
14
|
+
async function fileExists(path) {
|
|
15
|
+
try {
|
|
16
|
+
await access(path);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function addHost(hosts, host) {
|
|
24
|
+
if (!hosts.includes(host)) {
|
|
25
|
+
hosts.push(host);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Detect every project host that should receive Planu-owned assets.
|
|
30
|
+
* Environment identifies the active MCP host; files identify other checked-in
|
|
31
|
+
* host configs that should stay in sync too.
|
|
32
|
+
*/
|
|
33
|
+
export async function detectInitAssetHosts(projectPath) {
|
|
34
|
+
const hosts = [];
|
|
35
|
+
const activeHost = detectHost();
|
|
36
|
+
if (activeHost === 'claude-code' || activeHost === 'codex' || activeHost === 'gemini') {
|
|
37
|
+
addHost(hosts, activeHost);
|
|
38
|
+
}
|
|
39
|
+
const checks = await Promise.all([
|
|
40
|
+
fileExists(join(projectPath, 'CLAUDE.md')),
|
|
41
|
+
fileExists(join(projectPath, '.claude')),
|
|
42
|
+
fileExists(join(projectPath, 'AGENTS.md')),
|
|
43
|
+
fileExists(join(projectPath, '.openai')),
|
|
44
|
+
fileExists(join(projectPath, 'GEMINI.md')),
|
|
45
|
+
fileExists(join(projectPath, '.gemini')),
|
|
46
|
+
fileExists(join(projectPath, 'opencode.json')),
|
|
47
|
+
fileExists(join(projectPath, '.opencode')),
|
|
48
|
+
]);
|
|
49
|
+
const [hasClaudeMd, hasClaudeDir, hasAgentsMd, hasOpenAiDir, hasGeminiMd, hasGeminiDir, hasOpenCodeJson, hasOpenCodeDir,] = checks;
|
|
50
|
+
if (hasClaudeMd || hasClaudeDir) {
|
|
51
|
+
addHost(hosts, 'claude-code');
|
|
52
|
+
}
|
|
53
|
+
if (hasAgentsMd || hasOpenAiDir) {
|
|
54
|
+
addHost(hosts, 'codex');
|
|
55
|
+
}
|
|
56
|
+
if (hasGeminiMd || hasGeminiDir) {
|
|
57
|
+
addHost(hosts, 'gemini');
|
|
58
|
+
}
|
|
59
|
+
if (hasOpenCodeJson || hasOpenCodeDir) {
|
|
60
|
+
addHost(hosts, 'opencode');
|
|
61
|
+
}
|
|
62
|
+
// Portable default for new/unknown projects: AGENTS.md, not Claude-specific files.
|
|
63
|
+
if (hosts.length === 0) {
|
|
64
|
+
addHost(hosts, 'codex');
|
|
65
|
+
}
|
|
66
|
+
return hosts;
|
|
67
|
+
}
|
|
68
|
+
async function installRulesForHost(projectPath, host) {
|
|
69
|
+
try {
|
|
70
|
+
const { installUniversalRules } = await import('../../engine/universal-rules/installer.js');
|
|
71
|
+
return await installUniversalRules(projectPath, host);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function parseSkillTemplate(raw, fallbackName) {
|
|
78
|
+
const frontmatter = /^---\n([\s\S]*?)\n---\n?/.exec(raw);
|
|
79
|
+
const name = /(?:^|\n)name:\s*([^\n]+)/.exec(frontmatter?.[1] ?? '')?.[1]?.trim() ??
|
|
80
|
+
fallbackName.replace(/\.md$/, '');
|
|
81
|
+
const description = /(?:^|\n)description:\s*([^\n]+)/.exec(frontmatter?.[1] ?? '')?.[1]?.trim() ??
|
|
82
|
+
`Planu workflow skill: ${name}`;
|
|
83
|
+
const content = frontmatter ? raw.slice(frontmatter[0].length) : raw;
|
|
84
|
+
return {
|
|
85
|
+
name,
|
|
86
|
+
description: description.replace(/^["']|["']$/g, ''),
|
|
87
|
+
content: content.trimEnd() + '\n',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
async function readCoreSkillTemplate(fileName) {
|
|
91
|
+
try {
|
|
92
|
+
const { fileURLToPath } = await import('node:url');
|
|
93
|
+
const templatePath = join(fileURLToPath(new URL('.', import.meta.url)), '../../config/skill-templates', fileName);
|
|
94
|
+
const raw = await readFile(templatePath, 'utf-8');
|
|
95
|
+
return parseSkillTemplate(raw, fileName);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async function installCoreSkillsForHost(projectPath, host) {
|
|
102
|
+
const installed = [];
|
|
103
|
+
for (const fileName of CORE_SKILL_TEMPLATES) {
|
|
104
|
+
const template = await readCoreSkillTemplate(fileName);
|
|
105
|
+
if (template === null) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const result = await handleCreateSkill({
|
|
109
|
+
projectPath,
|
|
110
|
+
host,
|
|
111
|
+
name: template.name,
|
|
112
|
+
description: template.description,
|
|
113
|
+
content: template.content,
|
|
114
|
+
overwriteExisting: false,
|
|
115
|
+
});
|
|
116
|
+
if (result.isError === true) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const path = typeof result.structuredContent === 'object' &&
|
|
120
|
+
'skillFilePath' in result.structuredContent &&
|
|
121
|
+
typeof result.structuredContent.skillFilePath === 'string'
|
|
122
|
+
? result.structuredContent.skillFilePath
|
|
123
|
+
: host === 'codex'
|
|
124
|
+
? join(projectPath, 'AGENTS.md')
|
|
125
|
+
: host === 'gemini'
|
|
126
|
+
? join(projectPath, '.gemini', 'skills', `${template.name}.md`)
|
|
127
|
+
: join(projectPath, '.claude', 'skills', template.name, 'SKILL.md');
|
|
128
|
+
installed.push({ host, name: template.name, path });
|
|
129
|
+
}
|
|
130
|
+
return installed;
|
|
131
|
+
}
|
|
132
|
+
async function scaffoldHostConfig(projectPath, host) {
|
|
133
|
+
try {
|
|
134
|
+
if (host === 'codex') {
|
|
135
|
+
const { scaffoldCodexConfig } = await import('../../hosts/codex/config-scaffold.js');
|
|
136
|
+
await scaffoldCodexConfig(projectPath);
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
if (host === 'gemini') {
|
|
140
|
+
const { scaffoldGeminiConfig } = await import('../../hosts/gemini/config-scaffold.js');
|
|
141
|
+
await scaffoldGeminiConfig(projectPath);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
if (host === 'opencode') {
|
|
145
|
+
const { scaffoldOpenCodeConfig } = await import('../../engine/opencode/config-scaffold.js');
|
|
146
|
+
scaffoldOpenCodeConfig(projectPath);
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
export async function installInitHostAssets(projectPath, _knowledge) {
|
|
156
|
+
const detectedHosts = await detectInitAssetHosts(projectPath);
|
|
157
|
+
const rulesInstalled = [];
|
|
158
|
+
const skillsInstalled = [];
|
|
159
|
+
let opencodeConfigured = false;
|
|
160
|
+
for (const host of detectedHosts) {
|
|
161
|
+
const configured = await scaffoldHostConfig(projectPath, host);
|
|
162
|
+
if (host === 'opencode') {
|
|
163
|
+
opencodeConfigured ||= configured;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
rulesInstalled.push(...(await installRulesForHost(projectPath, host)));
|
|
167
|
+
skillsInstalled.push(...(await installCoreSkillsForHost(projectPath, host)));
|
|
168
|
+
}
|
|
169
|
+
return { detectedHosts, rulesInstalled, skillsInstalled, opencodeConfigured };
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=host-assets-writer.js.map
|
|
@@ -44,6 +44,14 @@ export interface ScaffoldWriteResult {
|
|
|
44
44
|
pluginsInstalled: Pick<PluginInstallResult, 'marketplaceAdded' | 'installed' | 'skipped' | 'failed'> | null;
|
|
45
45
|
/** SPEC-685: Number of workflow skill files written by generateWorkflowSkills. */
|
|
46
46
|
workflowSkillsWritten: number;
|
|
47
|
+
/** Hosts that received Planu init assets (rules/skills/config). */
|
|
48
|
+
detectedAssetHosts: string[];
|
|
49
|
+
/** Number of host-aware universal rules written by init_project. */
|
|
50
|
+
hostRulesWritten: number;
|
|
51
|
+
/** Number of host-aware core skills written by init_project. */
|
|
52
|
+
hostSkillsWritten: number;
|
|
53
|
+
/** Whether OpenCode Planu assets were configured. */
|
|
54
|
+
opencodeConfigScaffolded: boolean;
|
|
47
55
|
}
|
|
48
56
|
/**
|
|
49
57
|
* Write all scaffold files to disk:
|
|
@@ -27,6 +27,7 @@ import { generateWorkflowSkills } from '../../engine/skill-generator/workflow-sk
|
|
|
27
27
|
* - ESLint/Prettier config
|
|
28
28
|
* - Architecture rules
|
|
29
29
|
*/
|
|
30
|
+
// eslint-disable-next-line complexity
|
|
30
31
|
export async function runScaffoldWriter(projectPath, projectId, knowledge, recommendedSkills, permissionsMode, pluginsMode) {
|
|
31
32
|
// Generate and write AI rules files
|
|
32
33
|
const rulesResult = await runRulesWriter(projectPath, projectId, knowledge, recommendedSkills);
|
|
@@ -44,15 +45,33 @@ export async function runScaffoldWriter(projectPath, projectId, knowledge, recom
|
|
|
44
45
|
/* best-effort */
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
|
-
|
|
48
|
-
let
|
|
48
|
+
let detectedAssetHosts = [];
|
|
49
|
+
let hostRulesWritten = 0;
|
|
50
|
+
let hostSkillsWritten = 0;
|
|
51
|
+
let opencodeConfigScaffolded = false;
|
|
49
52
|
try {
|
|
50
|
-
const
|
|
51
|
-
|
|
53
|
+
const { installInitHostAssets } = await import('./host-assets-writer.js');
|
|
54
|
+
const hostAssets = await installInitHostAssets(projectPath, knowledge);
|
|
55
|
+
detectedAssetHosts = hostAssets.detectedHosts;
|
|
56
|
+
hostRulesWritten = hostAssets.rulesInstalled.length;
|
|
57
|
+
hostSkillsWritten = hostAssets.skillsInstalled.length;
|
|
58
|
+
opencodeConfigScaffolded = hostAssets.opencodeConfigured;
|
|
52
59
|
}
|
|
53
60
|
catch {
|
|
54
61
|
/* best-effort */
|
|
55
62
|
}
|
|
63
|
+
const shouldWriteClaudeAssets = detectedAssetHosts.includes('claude-code');
|
|
64
|
+
// Auto-inject SDD workflow into CLAUDE.md (best-effort, idempotent)
|
|
65
|
+
let claudeMdUpdated = false;
|
|
66
|
+
if (shouldWriteClaudeAssets) {
|
|
67
|
+
try {
|
|
68
|
+
const projectSections = buildProjectSections(knowledge);
|
|
69
|
+
claudeMdUpdated = await injectSddIntoClaude(projectPath, projectSections);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
/* best-effort */
|
|
73
|
+
}
|
|
74
|
+
}
|
|
56
75
|
// SPEC-178: Scaffold ESLint and Prettier config if missing (best-effort)
|
|
57
76
|
let eslintCreated = false;
|
|
58
77
|
let prettierCreated = false;
|
|
@@ -70,69 +89,85 @@ export async function runScaffoldWriter(projectPath, projectId, knowledge, recom
|
|
|
70
89
|
const { written: architectureRulesWritten, skipped: architectureRulesSkipped } = generateAndWriteRules(projectPath);
|
|
71
90
|
// SPEC-263: Inject Planu SDD Workflow section into CLAUDE.md (idempotent, best-effort)
|
|
72
91
|
let planuWorkflowInjected = false;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
92
|
+
if (shouldWriteClaudeAssets) {
|
|
93
|
+
try {
|
|
94
|
+
const claudeMdPath = `${projectPath}/CLAUDE.md`;
|
|
95
|
+
const section = generatePlanuSection(projectPath, projectId);
|
|
96
|
+
await injectPlanuSection(claudeMdPath, section);
|
|
97
|
+
planuWorkflowInjected = true;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
/* best-effort */
|
|
101
|
+
}
|
|
81
102
|
}
|
|
82
103
|
// SPEC-263: Configure Planu hooks in .claude.json (idempotent, best-effort)
|
|
83
104
|
let planuHooksConfigured = false;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
105
|
+
if (shouldWriteClaudeAssets) {
|
|
106
|
+
try {
|
|
107
|
+
planuHooksConfigured = await configurePlanuHooks(projectPath);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
/* best-effort */
|
|
111
|
+
}
|
|
89
112
|
}
|
|
90
113
|
// SPEC-263: Write .claude/rules/planu-workflow.md if missing (best-effort)
|
|
91
114
|
let planuRulesWritten = false;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
115
|
+
if (shouldWriteClaudeAssets) {
|
|
116
|
+
try {
|
|
117
|
+
planuRulesWritten = await generateWorkflowRulesIfMissing(projectPath);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
/* best-effort */
|
|
121
|
+
}
|
|
97
122
|
}
|
|
98
123
|
// SPEC-291: Write .claude/rules/agent-teams.md if missing (best-effort)
|
|
99
124
|
let agentTeamsRulesWritten = false;
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
125
|
+
if (shouldWriteClaudeAssets) {
|
|
126
|
+
try {
|
|
127
|
+
agentTeamsRulesWritten = await generateAgentTeamsRulesIfMissing(projectPath);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
/* best-effort */
|
|
131
|
+
}
|
|
105
132
|
}
|
|
106
133
|
// SPEC-494: Write .claude/rules/planu-modes.md if missing (best-effort)
|
|
107
134
|
let modeRulesWritten = false;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
135
|
+
if (shouldWriteClaudeAssets) {
|
|
136
|
+
try {
|
|
137
|
+
modeRulesWritten = await generateModeRulesIfMissing(projectPath);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
/* best-effort */
|
|
141
|
+
}
|
|
113
142
|
}
|
|
114
143
|
// SPEC-517: Write .claude/rules/planu-response-style.md if missing (best-effort)
|
|
115
144
|
let responseStyleRulesWritten = false;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
145
|
+
if (shouldWriteClaudeAssets) {
|
|
146
|
+
try {
|
|
147
|
+
responseStyleRulesWritten = await generateResponseStyleRulesIfMissing(projectPath);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
/* best-effort */
|
|
151
|
+
}
|
|
121
152
|
}
|
|
122
153
|
// SPEC-519: Write .claude/skills/compact.md if missing (best-effort)
|
|
123
154
|
let compactSkillWritten = false;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
155
|
+
if (shouldWriteClaudeAssets) {
|
|
156
|
+
try {
|
|
157
|
+
compactSkillWritten = await generateCompactSkillIfMissing(projectPath);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
/* best-effort */
|
|
161
|
+
}
|
|
129
162
|
}
|
|
130
163
|
// SPEC-655: Write .claude/skills/find-skills.md if missing (best-effort, idempotent)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
164
|
+
if (shouldWriteClaudeAssets) {
|
|
165
|
+
try {
|
|
166
|
+
await generateFindSkillsIfMissing(projectPath);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
/* best-effort */
|
|
170
|
+
}
|
|
136
171
|
}
|
|
137
172
|
// SPEC-347: Inject git auto-stage snippet for planu HTML files (best-effort)
|
|
138
173
|
let gitAutoStageInjected = false;
|
|
@@ -167,11 +202,13 @@ export async function runScaffoldWriter(projectPath, projectId, knowledge, recom
|
|
|
167
202
|
}
|
|
168
203
|
// SPEC-530: Generate .claude/rules/conventions.md (best-effort, idempotent)
|
|
169
204
|
let conventionsMdWritten = false;
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
205
|
+
if (shouldWriteClaudeAssets) {
|
|
206
|
+
try {
|
|
207
|
+
conventionsMdWritten = await generateConventionsMdIfMissing(projectPath, knowledge);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
/* best-effort */
|
|
211
|
+
}
|
|
175
212
|
}
|
|
176
213
|
// SPEC-591: Scaffold .gemini/ if a Gemini workspace marker is present (best-effort, idempotent)
|
|
177
214
|
let geminiConfigScaffolded = false;
|
|
@@ -188,11 +225,14 @@ export async function runScaffoldWriter(projectPath, projectId, knowledge, recom
|
|
|
188
225
|
}
|
|
189
226
|
// SPEC-593: Write .claude/skills/planu-multi-teammate-review.md if missing (best-effort, idempotent)
|
|
190
227
|
let multiTeammateReviewSkillWritten = false;
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
228
|
+
if (shouldWriteClaudeAssets) {
|
|
229
|
+
try {
|
|
230
|
+
multiTeammateReviewSkillWritten =
|
|
231
|
+
await generateMultiTeammateReviewSkillIfMissing(projectPath);
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
/* best-effort */
|
|
235
|
+
}
|
|
196
236
|
}
|
|
197
237
|
// SPEC-592: Scaffold Codex workspace config if Codex env is detected (best-effort, no-op outside Codex)
|
|
198
238
|
let codexConfigScaffolded = false;
|
|
@@ -213,17 +253,19 @@ export async function runScaffoldWriter(projectPath, projectId, knowledge, recom
|
|
|
213
253
|
// (idempotent — skips files already up-to-date; best-effort, no-op elsewhere)
|
|
214
254
|
let planuSubagentsScaffolded = false;
|
|
215
255
|
let planuUxSkillsScaffolded = 0;
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
256
|
+
if (shouldWriteClaudeAssets) {
|
|
257
|
+
try {
|
|
258
|
+
const { scaffoldPlanuSubagents, scaffoldPlanuSkills } = await import('../../hosts/claude-code/ux/index.js');
|
|
259
|
+
const [subagentResult, skillResult] = await Promise.all([
|
|
260
|
+
scaffoldPlanuSubagents(projectPath),
|
|
261
|
+
scaffoldPlanuSkills(projectPath),
|
|
262
|
+
]);
|
|
263
|
+
planuSubagentsScaffolded = subagentResult.created.length > 0;
|
|
264
|
+
planuUxSkillsScaffolded = skillResult.created.length;
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
/* best-effort */
|
|
268
|
+
}
|
|
227
269
|
}
|
|
228
270
|
// SPEC-594: Configure .claude/settings.json permissions (opt-in; idempotent; best-effort)
|
|
229
271
|
let permissionsConfigured = null;
|
|
@@ -282,12 +324,14 @@ export async function runScaffoldWriter(projectPath, projectId, knowledge, recom
|
|
|
282
324
|
}
|
|
283
325
|
// SPEC-685: Generate all 22 workflow skill files (idempotent, best-effort)
|
|
284
326
|
let workflowSkillsWritten = 0;
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
327
|
+
if (shouldWriteClaudeAssets) {
|
|
328
|
+
try {
|
|
329
|
+
const skillResult = await generateWorkflowSkills(projectPath, knowledge);
|
|
330
|
+
workflowSkillsWritten = skillResult.written.length;
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
/* best-effort — never fail init_project */
|
|
334
|
+
}
|
|
291
335
|
}
|
|
292
336
|
// SPEC-587: Install spec-sanctity PostToolUse hook when Claude Code is detected
|
|
293
337
|
// (idempotent — overwrites only when content differs; best-effort, no-op elsewhere)
|
|
@@ -364,6 +408,10 @@ export async function runScaffoldWriter(projectPath, projectId, knowledge, recom
|
|
|
364
408
|
permissionsConfigured,
|
|
365
409
|
pluginsInstalled,
|
|
366
410
|
workflowSkillsWritten,
|
|
411
|
+
detectedAssetHosts,
|
|
412
|
+
hostRulesWritten,
|
|
413
|
+
hostSkillsWritten,
|
|
414
|
+
opencodeConfigScaffolded,
|
|
367
415
|
};
|
|
368
416
|
}
|
|
369
417
|
//# sourceMappingURL=scaffold-writer.js.map
|