@planu/cli 3.9.5 → 3.9.7
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 +5 -0
- package/dist/config/skill-templates/planu-context-assets.md +94 -0
- package/dist/engine/universal-rules/catalog.js +8 -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/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/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: planu-context-assets
|
|
3
|
+
description: Create project-specific Planu rules or skills from the current app context so recurring standards and workflows become reusable agent guidance instead of MCP tool bloat.
|
|
4
|
+
triggers:
|
|
5
|
+
- create project rules
|
|
6
|
+
- create project skills
|
|
7
|
+
- capture this workflow
|
|
8
|
+
- make this a rule
|
|
9
|
+
- make this a skill
|
|
10
|
+
- codify this pattern
|
|
11
|
+
- project context changed
|
|
12
|
+
- app conventions
|
|
13
|
+
version: 1.0.0
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# /planu-context-assets — Create Rules or Skills from App Context
|
|
17
|
+
|
|
18
|
+
## When to invoke
|
|
19
|
+
|
|
20
|
+
Use this skill when project context reveals a recurring convention, workflow, integration, domain rule, or implementation pattern that future agents should reuse.
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
|
|
24
|
+
- A design system rule is discovered while implementing UI.
|
|
25
|
+
- A release or deployment workflow becomes stable.
|
|
26
|
+
- A legacy cleanup pattern repeats across modules.
|
|
27
|
+
- A domain invariant must never be violated.
|
|
28
|
+
- A framework-specific project convention is discovered during analysis.
|
|
29
|
+
|
|
30
|
+
## Decision rule
|
|
31
|
+
|
|
32
|
+
Create a **rule** when the guidance is mandatory and should constrain every future agent:
|
|
33
|
+
|
|
34
|
+
- architecture boundaries
|
|
35
|
+
- spec quality gates
|
|
36
|
+
- release policy
|
|
37
|
+
- BDD/SDD invariants
|
|
38
|
+
- security or data handling rules
|
|
39
|
+
- "never do X" / "always do Y" project standards
|
|
40
|
+
|
|
41
|
+
Create a **skill** when the guidance is an optional workflow invoked for a specific task:
|
|
42
|
+
|
|
43
|
+
- release workflow
|
|
44
|
+
- Figma implementation workflow
|
|
45
|
+
- legacy characterization workflow
|
|
46
|
+
- domain bundle generation
|
|
47
|
+
- integration setup
|
|
48
|
+
- migration playbook
|
|
49
|
+
- debugging or audit procedure
|
|
50
|
+
|
|
51
|
+
Do not create either when the information is one-off, speculative, or already covered by an existing rule/skill.
|
|
52
|
+
|
|
53
|
+
## Workflow
|
|
54
|
+
|
|
55
|
+
1. Inspect the current context and existing project assets:
|
|
56
|
+
- `AGENTS.md`
|
|
57
|
+
- `CLAUDE.md`
|
|
58
|
+
- `.claude/rules/`
|
|
59
|
+
- `.claude/skills/`
|
|
60
|
+
- `.gemini/conventions.md`
|
|
61
|
+
- `.gemini/skills/`
|
|
62
|
+
- `.opencode/rules/`
|
|
63
|
+
- `.opencode/skills/`
|
|
64
|
+
2. Decide whether the new asset is a rule, a skill, or nothing.
|
|
65
|
+
3. Keep the asset small and operational:
|
|
66
|
+
- one purpose
|
|
67
|
+
- explicit trigger
|
|
68
|
+
- concrete steps or constraints
|
|
69
|
+
- verification command when applicable
|
|
70
|
+
4. Use Planu host-aware tools:
|
|
71
|
+
- `create_rule` for mandatory constraints
|
|
72
|
+
- `create_skill` for reusable workflows
|
|
73
|
+
5. Use `host: "auto"` unless the user explicitly targets a host.
|
|
74
|
+
6. Prefer updating an existing Planu-owned asset over creating a duplicate.
|
|
75
|
+
|
|
76
|
+
## Quality bar
|
|
77
|
+
|
|
78
|
+
- Rules must be enforceable and written as constraints.
|
|
79
|
+
- Skills must describe when to invoke, what to inspect, what to do, and what output to produce.
|
|
80
|
+
- Do not reference legacy MCP tools that are not part of the 14-tool public core.
|
|
81
|
+
- Do not create broad "misc" skills.
|
|
82
|
+
- Do not preserve dead workflows "just in case".
|
|
83
|
+
|
|
84
|
+
## Output
|
|
85
|
+
|
|
86
|
+
Report:
|
|
87
|
+
|
|
88
|
+
- asset type: rule or skill
|
|
89
|
+
- asset name
|
|
90
|
+
- host target
|
|
91
|
+
- path or file updated
|
|
92
|
+
- why it should exist
|
|
93
|
+
- when it should trigger
|
|
94
|
+
|
|
@@ -4,12 +4,20 @@ import { planuDogfoodBugsRule } from './rules/planu-dogfood-bugs.js';
|
|
|
4
4
|
import { planuWorkflowRule } from './rules/planu-workflow.js';
|
|
5
5
|
import { planuModesRule } from './rules/planu-modes.js';
|
|
6
6
|
import { agentTeamsRule } from './rules/agent-teams.js';
|
|
7
|
+
import { planuEnglishSpecsRule } from './rules/planu-english-specs.js';
|
|
8
|
+
import { planuBddCriteriaRule } from './rules/planu-bdd-criteria.js';
|
|
9
|
+
import { planuApprovalGatesRule } from './rules/planu-approval-gates.js';
|
|
10
|
+
import { planuReleasePolicyRule } from './rules/planu-release-policy.js';
|
|
7
11
|
/**
|
|
8
12
|
* The full catalog of universal Planu rules.
|
|
9
13
|
* Order matters: rules are installed in catalog order.
|
|
10
14
|
*/
|
|
11
15
|
export const UNIVERSAL_RULES = [
|
|
12
16
|
planuWorkflowRule,
|
|
17
|
+
planuEnglishSpecsRule,
|
|
18
|
+
planuBddCriteriaRule,
|
|
19
|
+
planuApprovalGatesRule,
|
|
20
|
+
planuReleasePolicyRule,
|
|
13
21
|
planuModesRule,
|
|
14
22
|
agentTeamsRule,
|
|
15
23
|
planuDogfoodBugsRule,
|
|
@@ -6,8 +6,14 @@ import { UNIVERSAL_RULES } from './catalog.js';
|
|
|
6
6
|
import { writeRuleForHost } from './host-writer.js';
|
|
7
7
|
import { hashContent, readManifest, upsertManifestEntry } from './user-edit-detector.js';
|
|
8
8
|
/** Path of the manifest relative to projectPath. */
|
|
9
|
-
function manifestPath(projectPath) {
|
|
10
|
-
|
|
9
|
+
function manifestPath(projectPath, host) {
|
|
10
|
+
if (host === 'claude-code') {
|
|
11
|
+
return join(projectPath, '.claude', 'rules', '.planu-rules-manifest.json');
|
|
12
|
+
}
|
|
13
|
+
if (host === 'gemini') {
|
|
14
|
+
return join(projectPath, '.gemini', '.planu-rules-manifest.json');
|
|
15
|
+
}
|
|
16
|
+
return join(projectPath, '.planu', 'rules-manifest.codex.json');
|
|
11
17
|
}
|
|
12
18
|
/**
|
|
13
19
|
* Install all default-enabled universal rules applicable to `host`.
|
|
@@ -15,7 +21,7 @@ function manifestPath(projectPath) {
|
|
|
15
21
|
* Returns the list of rules that were written.
|
|
16
22
|
*/
|
|
17
23
|
export async function installUniversalRules(projectPath, host) {
|
|
18
|
-
const mPath = manifestPath(projectPath);
|
|
24
|
+
const mPath = manifestPath(projectPath, host);
|
|
19
25
|
const manifest = (await readManifest(mPath)) ?? { rules: [] };
|
|
20
26
|
const installed = [];
|
|
21
27
|
for (const rule of UNIVERSAL_RULES) {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// engine/universal-rules/rules/planu-approval-gates.ts — Universal rule: approval and done gates
|
|
2
|
+
function buildBody() {
|
|
3
|
+
return `# Planu Approval and Done Gates
|
|
4
|
+
|
|
5
|
+
Auto-generated by \`init_project\`. Do not edit manually.
|
|
6
|
+
|
|
7
|
+
## Approval Gate
|
|
8
|
+
|
|
9
|
+
Before moving a spec to \`approved\`, run and pass:
|
|
10
|
+
|
|
11
|
+
1. \`challenge_spec\`
|
|
12
|
+
2. \`check_readiness\`
|
|
13
|
+
3. BDD criteria completeness
|
|
14
|
+
4. files-to-create / files-to-modify ownership
|
|
15
|
+
5. test plan and verification commands
|
|
16
|
+
|
|
17
|
+
If the user explicitly forces approval, record the reason in the audit trail and make the missing risk visible.
|
|
18
|
+
|
|
19
|
+
## Done Gate
|
|
20
|
+
|
|
21
|
+
Before moving a spec to \`done\`, run \`validate\`.
|
|
22
|
+
|
|
23
|
+
If implementation intentionally diverged from the approved spec, run \`reconcile_spec\` first and make the divergence explicit before marking done.
|
|
24
|
+
|
|
25
|
+
## Hard Blocks
|
|
26
|
+
|
|
27
|
+
- Do not approve specs with placeholders.
|
|
28
|
+
- Do not mark done without validation evidence.
|
|
29
|
+
- Do not hide intentional drift; reconcile it.
|
|
30
|
+
`;
|
|
31
|
+
}
|
|
32
|
+
export const planuApprovalGatesRule = {
|
|
33
|
+
id: 'planu-approval-gates',
|
|
34
|
+
name: 'Planu Approval Gates',
|
|
35
|
+
description: 'Requires challenge/readiness before approval and validate/reconcile before done.',
|
|
36
|
+
category: 'safety',
|
|
37
|
+
applicableHosts: ['all'],
|
|
38
|
+
defaultEnabled: true,
|
|
39
|
+
buildContent: (_host) => buildBody(),
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=planu-approval-gates.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// engine/universal-rules/rules/planu-bdd-criteria.ts — Universal rule: BDD acceptance criteria
|
|
2
|
+
function buildBody() {
|
|
3
|
+
return `# Planu BDD Acceptance Criteria
|
|
4
|
+
|
|
5
|
+
Auto-generated by \`init_project\`. Do not edit manually.
|
|
6
|
+
|
|
7
|
+
## Rule
|
|
8
|
+
|
|
9
|
+
Every acceptance criterion must be executable enough for an implementation agent and a validation gate.
|
|
10
|
+
|
|
11
|
+
Required format:
|
|
12
|
+
|
|
13
|
+
\`\`\`gherkin
|
|
14
|
+
GIVEN <initial state or context>
|
|
15
|
+
WHEN <action or event>
|
|
16
|
+
THEN <observable result>
|
|
17
|
+
AND <additional observable constraint>
|
|
18
|
+
\`\`\`
|
|
19
|
+
|
|
20
|
+
## Required Detail
|
|
21
|
+
|
|
22
|
+
Each criterion must identify:
|
|
23
|
+
|
|
24
|
+
- files or modules that are expected to change when known
|
|
25
|
+
- user-visible behavior or API behavior
|
|
26
|
+
- test expectation or verification command
|
|
27
|
+
- ownership when the work spans more than one wave or agent
|
|
28
|
+
|
|
29
|
+
## Hard Blocks
|
|
30
|
+
|
|
31
|
+
- No loose checklist-only acceptance criteria.
|
|
32
|
+
- No vague outcomes such as "works correctly", "improves UX", or "handles errors" without observable behavior.
|
|
33
|
+
- No approval when criteria are missing \`GIVEN\`, \`WHEN\`, or \`THEN\`.
|
|
34
|
+
`;
|
|
35
|
+
}
|
|
36
|
+
export const planuBddCriteriaRule = {
|
|
37
|
+
id: 'planu-bdd-criteria',
|
|
38
|
+
name: 'Planu BDD Criteria',
|
|
39
|
+
description: 'Requires complete GIVEN/WHEN/THEN acceptance criteria.',
|
|
40
|
+
category: 'quality',
|
|
41
|
+
applicableHosts: ['all'],
|
|
42
|
+
defaultEnabled: true,
|
|
43
|
+
buildContent: (_host) => buildBody(),
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=planu-bdd-criteria.js.map
|
|
@@ -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,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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planu/cli",
|
|
3
|
-
"version": "3.9.
|
|
3
|
+
"version": "3.9.7",
|
|
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,12 +32,12 @@
|
|
|
32
32
|
"packageName": "@planu/core"
|
|
33
33
|
},
|
|
34
34
|
"optionalDependencies": {
|
|
35
|
-
"@planu/core-darwin-arm64": "3.
|
|
36
|
-
"@planu/core-darwin-x64": "3.
|
|
37
|
-
"@planu/core-linux-arm64-gnu": "3.
|
|
38
|
-
"@planu/core-linux-arm64-musl": "3.
|
|
39
|
-
"@planu/core-linux-x64-gnu": "3.
|
|
40
|
-
"@planu/core-linux-x64-musl": "3.
|
|
35
|
+
"@planu/core-darwin-arm64": "3.9.7",
|
|
36
|
+
"@planu/core-darwin-x64": "3.9.7",
|
|
37
|
+
"@planu/core-linux-arm64-gnu": "3.9.7",
|
|
38
|
+
"@planu/core-linux-arm64-musl": "3.9.7",
|
|
39
|
+
"@planu/core-linux-x64-gnu": "3.9.7",
|
|
40
|
+
"@planu/core-linux-x64-musl": "3.9.7"
|
|
41
41
|
},
|
|
42
42
|
"engines": {
|
|
43
43
|
"node": ">=24.0.0"
|