@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.
Files changed (37) hide show
  1. package/dist/config/skill-templates/planu-context-assets.md +94 -0
  2. package/dist/engine/handoff-packager.js +151 -4
  3. package/dist/engine/sdd-model-routing.d.ts +16 -0
  4. package/dist/engine/sdd-model-routing.js +195 -0
  5. package/dist/engine/universal-rules/catalog.js +10 -0
  6. package/dist/engine/universal-rules/installer.js +9 -3
  7. package/dist/engine/universal-rules/rules/planu-approval-gates.d.ts +3 -0
  8. package/dist/engine/universal-rules/rules/planu-approval-gates.js +41 -0
  9. package/dist/engine/universal-rules/rules/planu-bdd-criteria.d.ts +3 -0
  10. package/dist/engine/universal-rules/rules/planu-bdd-criteria.js +45 -0
  11. package/dist/engine/universal-rules/rules/planu-english-specs.d.ts +3 -0
  12. package/dist/engine/universal-rules/rules/planu-english-specs.js +36 -0
  13. package/dist/engine/universal-rules/rules/planu-release-policy.d.ts +3 -0
  14. package/dist/engine/universal-rules/rules/planu-release-policy.js +38 -0
  15. package/dist/engine/universal-rules/rules/planu-sdd-model-routing.d.ts +3 -0
  16. package/dist/engine/universal-rules/rules/planu-sdd-model-routing.js +51 -0
  17. package/dist/tools/init-project/host-assets-writer.d.ts +21 -0
  18. package/dist/tools/init-project/host-assets-writer.js +171 -0
  19. package/dist/tools/init-project/scaffold-writer.d.ts +8 -0
  20. package/dist/tools/init-project/scaffold-writer.js +122 -74
  21. package/dist/tools/package-handoff.js +76 -0
  22. package/dist/tools/update-status/file-sync.d.ts +1 -0
  23. package/dist/tools/update-status/file-sync.js +1 -0
  24. package/dist/tools/update-status/index.js +88 -0
  25. package/dist/types/index.d.ts +1 -0
  26. package/dist/types/index.js +1 -0
  27. package/dist/types/readiness.d.ts +10 -1
  28. package/dist/types/sdd-model-routing.d.ts +22 -0
  29. package/dist/types/sdd-model-routing.js +2 -0
  30. package/dist/types/spec/inputs.d.ts +23 -0
  31. package/package.json +7 -7
  32. package/dist/types/data/estimation.d.ts +0 -147
  33. package/dist/types/data/estimation.js +0 -2
  34. package/dist/types/data/index.d.ts +0 -5
  35. package/dist/types/data/index.js +0 -6
  36. package/dist/types/data/velocity.d.ts +0 -168
  37. 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,3 @@
1
+ import type { UniversalRule } from '../../../types/universal-rules/index.js';
2
+ export declare const planuReleasePolicyRule: UniversalRule;
3
+ //# sourceMappingURL=planu-release-policy.d.ts.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,3 @@
1
+ import type { UniversalRule } from '../../../types/universal-rules/index.js';
2
+ export declare const planuSddModelRoutingRule: UniversalRule;
3
+ //# sourceMappingURL=planu-sdd-model-routing.d.ts.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
- // Auto-inject SDD workflow into CLAUDE.md (best-effort, idempotent)
48
- let claudeMdUpdated = false;
48
+ let detectedAssetHosts = [];
49
+ let hostRulesWritten = 0;
50
+ let hostSkillsWritten = 0;
51
+ let opencodeConfigScaffolded = false;
49
52
  try {
50
- const projectSections = buildProjectSections(knowledge);
51
- claudeMdUpdated = await injectSddIntoClaude(projectPath, projectSections);
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
- try {
74
- const claudeMdPath = `${projectPath}/CLAUDE.md`;
75
- const section = generatePlanuSection(projectPath, projectId);
76
- await injectPlanuSection(claudeMdPath, section);
77
- planuWorkflowInjected = true;
78
- }
79
- catch {
80
- /* best-effort */
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
- try {
85
- planuHooksConfigured = await configurePlanuHooks(projectPath);
86
- }
87
- catch {
88
- /* best-effort */
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
- try {
93
- planuRulesWritten = await generateWorkflowRulesIfMissing(projectPath);
94
- }
95
- catch {
96
- /* best-effort */
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
- try {
101
- agentTeamsRulesWritten = await generateAgentTeamsRulesIfMissing(projectPath);
102
- }
103
- catch {
104
- /* best-effort */
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
- try {
109
- modeRulesWritten = await generateModeRulesIfMissing(projectPath);
110
- }
111
- catch {
112
- /* best-effort */
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
- try {
117
- responseStyleRulesWritten = await generateResponseStyleRulesIfMissing(projectPath);
118
- }
119
- catch {
120
- /* best-effort */
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
- try {
125
- compactSkillWritten = await generateCompactSkillIfMissing(projectPath);
126
- }
127
- catch {
128
- /* best-effort */
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
- try {
132
- await generateFindSkillsIfMissing(projectPath);
133
- }
134
- catch {
135
- /* best-effort */
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
- try {
171
- conventionsMdWritten = await generateConventionsMdIfMissing(projectPath, knowledge);
172
- }
173
- catch {
174
- /* best-effort */
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
- try {
192
- multiTeammateReviewSkillWritten = await generateMultiTeammateReviewSkillIfMissing(projectPath);
193
- }
194
- catch {
195
- /* best-effort */
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
- try {
217
- const { scaffoldPlanuSubagents, scaffoldPlanuSkills } = await import('../../hosts/claude-code/ux/index.js');
218
- const [subagentResult, skillResult] = await Promise.all([
219
- scaffoldPlanuSubagents(projectPath),
220
- scaffoldPlanuSkills(projectPath),
221
- ]);
222
- planuSubagentsScaffolded = subagentResult.created.length > 0;
223
- planuUxSkillsScaffolded = skillResult.created.length;
224
- }
225
- catch {
226
- /* best-effort */
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
- try {
286
- const skillResult = await generateWorkflowSkills(projectPath, knowledge);
287
- workflowSkillsWritten = skillResult.written.length;
288
- }
289
- catch {
290
- /* best-effort — never fail init_project */
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