prizmkit 1.1.142 → 1.1.144

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 (36) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/adapters/pi/paths.js +9 -0
  3. package/bundled/adapters/pi/rules-adapter.js +13 -0
  4. package/bundled/adapters/pi/settings-adapter.js +23 -0
  5. package/bundled/adapters/pi/skill-adapter.js +18 -0
  6. package/bundled/dev-pipeline/prizmkit_runtime/config.py +15 -3
  7. package/bundled/dev-pipeline/prizmkit_runtime/gitops.py +3 -1
  8. package/bundled/dev-pipeline/prizmkit_runtime/platform_detection.py +10 -2
  9. package/bundled/dev-pipeline/prizmkit_runtime/runner_bookkeeping.py +1 -0
  10. package/bundled/dev-pipeline/prizmkit_runtime/runner_recovery.py +1 -0
  11. package/bundled/dev-pipeline/prizmkit_runtime/sessions.py +26 -2
  12. package/bundled/dev-pipeline/scripts/parse-stream-progress.py +61 -7
  13. package/bundled/dev-pipeline/tests/test_unified_cli.py +88 -0
  14. package/bundled/skills/_metadata.json +1 -1
  15. package/bundled/skills/prizmkit-code-review/SKILL.md +3 -3
  16. package/bundled/skills/prizmkit-code-review/references/independent-code-review.md +63 -58
  17. package/bundled/skills/prizmkit-code-review/references/review-report-template.md +8 -1
  18. package/bundled/skills/prizmkit-plan/SKILL.md +3 -3
  19. package/bundled/skills/prizmkit-plan/assets/plan-template.md +3 -0
  20. package/bundled/skills/prizmkit-plan/references/independent-plan-review.md +55 -52
  21. package/bundled/skills/prizmkit-test/SKILL.md +3 -3
  22. package/bundled/skills/prizmkit-test/references/independent-test-review.md +68 -69
  23. package/bundled/skills/prizmkit-test/references/test-report-template.md +5 -1
  24. package/bundled/templates/hooks/commit-intent-status.py +50 -0
  25. package/bundled/templates/hooks/commit-intent.json +2 -2
  26. package/bundled/templates/hooks/post-command-prizm-drift.py +44 -0
  27. package/package.json +1 -1
  28. package/src/ai-cli-launch.js +1 -1
  29. package/src/clean.js +13 -4
  30. package/src/config.js +32 -5
  31. package/src/detect-platform.js +11 -7
  32. package/src/index.js +5 -0
  33. package/src/platforms.js +6 -2
  34. package/src/prompts.js +12 -4
  35. package/src/scaffold.js +74 -20
  36. package/src/upgrade.js +12 -5
@@ -18,66 +18,62 @@ Main Agent builds and executes project-native tests
18
18
  → TEST_PASS | TEST_NEEDS_FIXES | TEST_BLOCKED
19
19
  ```
20
20
 
21
- Create at most one Reviewer for this invocation. Reuse the exact same Reviewer through native continuation. Never reuse a Reviewer from another lifecycle stage and never create a replacement with a summary to simulate continuation.
21
+ Keep at most one Reviewer active for this invocation and permit at most one replacement. Prefer native continuation and never reuse a Reviewer from another lifecycle stage. A compliant replacement may continue the same shared response budget only with complete latest state, never only a conversation summary.
22
22
 
23
23
  ## Host Capability Gate
24
24
 
25
- The gate is all-or-nothing. Before creating a Reviewer, the host must prove this semantic execution contract:
25
+ The gate is all-or-nothing. Before creating a Reviewer, inspect the current host's actual execution-unit configuration and prove this semantic contract without relying on platform identity or implementation-specific parameters:
26
26
 
27
27
  ```yaml
28
28
  execution_unit:
29
- count: 1
30
- access: read-only
31
- mutation: unavailable
32
- arbitrary_command_execution: unavailable
29
+ concurrency: at-most-one-active-reviewer
30
+ workspace_access: read-only-active-checkout
31
+ mutation: structurally-unavailable
32
+ command_execution: structurally-unavailable
33
+ network_access: structurally-unavailable
34
+ external_process_execution: structurally-unavailable
33
35
  downstream_execution: structurally-unavailable
34
- context_continuation: same-unit-native-resume
35
- workspace_observation: bounded-active-checkout-input
36
36
  model_configuration: inherit-current-session
37
+ scope_expansion: concrete-coupling-only
38
+ continuation: prefer-same-unit
39
+ replacement: compliant-replacement-allowed
37
40
  ```
38
41
 
39
42
  Rules:
40
43
 
41
44
  - Prompt instructions cannot satisfy a missing structural capability.
42
- - Do not branch on platform identity, tool name, execution-unit type name, adapter output, or an allowlist.
43
- - A general execution unit that merely promises not to mutate, execute commands, or delegate is ineligible while those capabilities remain available.
45
+ - Do not branch on platform identity, provider, tool name, command name, execution-unit type name, adapter output, CLI parameter, or an allowlist.
46
+ - The Reviewer cannot create, modify, delete, rename, stage, or commit files; execute shell, Git, tests, builds, network calls, or external processes; or create, invoke, resume, or coordinate downstream execution units.
47
+ - A general execution unit that merely promises to stay read-only is ineligible while prohibited capabilities remain available.
44
48
  - The Reviewer inherits the current session's model configuration.
45
49
  - If any capability is missing or unproven, create no Reviewer and use Strict Downgrade.
46
50
 
47
- ## Bounded Immutable Review Input
51
+ ## Stage-Specific Review Input
48
52
 
49
- Before every response, the Main Agent captures a complete input that remains immutable for that response:
53
+ Resolve exact requirement identity from workflow state or explicit handoff on every response:
50
54
 
51
55
  ```text
52
- test-review-input
53
- ├── manifest
54
- │ ├── response number and input identity
55
- │ ├── exact changed production and test paths
56
- │ ├── relevant unchanged path inventory
57
- │ └── consistency markers
58
- ├── change context
59
- │ ├── requirement and acceptance criteria
60
- │ ├── current production change
61
- │ └── applicable project rules
62
- ├── coverage context
63
- │ ├── affected business module
64
- │ ├── observable behaviors
65
- │ ├── risk and boundary assessment
66
- │ └── Regression Ring
67
- ├── test context
68
- │ ├── existing, added, and modified tests
69
- │ ├── fixtures, mocks, and contracts
70
- │ ├── selected test layers
71
- │ └── native execution results
72
- └── targeted supporting context
73
- ├── relevant callers and consumers
74
- ├── shared schemas and contracts
75
- └── necessary public official contract excerpts
56
+ artifact_dir: [EXACT_ARTIFACT_DIR]
57
+ spec_path: [EXACT_SPEC_PATH]
58
+ plan_path: [EXACT_PLAN_PATH]
59
+ checkout_root: [ACTIVE_CHECKOUT_ROOT]
76
60
  ```
77
61
 
78
- The Reviewer does not run Git, tests, builds, network calls, or broad repository discovery. The Main Agent captures authoritative current state. Do not include secrets, credentials, production data, or unrelated private repository content.
62
+ These paths remain authoritative even when ignored by Git. Never ask the Reviewer to discover a latest artifact or guess among multiple `spec.md` and `plan.md` files.
79
63
 
80
- Before ordinary review, the Reviewer verifies that manifest and content agree. Missing paths, unexplained changed files, stale content, or mixed-response input produces `REVIEW_BLOCKED`, never partial success.
64
+ The Main Agent supplies:
65
+
66
+ - original requirement and confirmed clarifications;
67
+ - exact artifact, spec, and plan paths plus current contents;
68
+ - complete current production-and-test change;
69
+ - affected business module, observable behaviors, applicable risks, and Regression Ring;
70
+ - existing, added, and modified tests, fixtures, mocks, schemas, contracts, and selected layers;
71
+ - native test commands and results executed by Main Agent;
72
+ - test-stage repairs;
73
+ - response number and total budget;
74
+ - prior adjudication and actual corrections on continuation or replacement.
75
+
76
+ The Reviewer may use structurally read-only checkout access to inspect production code, tests, fixtures, schemas, callers, consumers, and contracts only through concrete coupling evidence. It cannot run Git, tests, builds, network calls, or external processes and must not perform an unconditional repository-wide scan. Do not include secrets, credentials, production data, or unrelated private content. Missing or inconsistent required input produces `REVIEW_BLOCKED`, never partial success.
81
77
 
82
78
  ## Initial Reviewer Prompt
83
79
 
@@ -91,12 +87,14 @@ Objectively determine whether the complete current project-native tests credibly
91
87
 
92
88
  Response:
93
89
  This is response [RESPONSE_NUMBER] of a maximum five responses.
94
- Review-input identity: [INPUT_ID]
95
- Manifest: [MANIFEST]
96
- Change context: [CHANGE_CONTEXT]
90
+ Artifact directory: [ARTIFACT_DIR]
91
+ Specification path and content: [SPEC_PATH_AND_CONTENT]
92
+ Plan path and content: [PLAN_PATH_AND_CONTENT]
93
+ Checkout root: [CHECKOUT_ROOT]
94
+ Requirement and clarifications: [REQUIREMENT_CONTEXT]
95
+ Current production-and-test change: [CURRENT_CHANGE]
97
96
  Coverage context: [COVERAGE_CONTEXT]
98
- Test context: [TEST_CONTEXT]
99
- Targeted supporting context: [SUPPORTING_CONTEXT]
97
+ Test context and Main-Agent execution results: [TEST_CONTEXT]
100
98
 
101
99
  Execution boundaries:
102
100
  - Complete this review personally.
@@ -104,9 +102,9 @@ Execution boundaries:
104
102
  - Do not ask the Main Agent to create a helper.
105
103
  - Do not re-enter orchestration, delegation, another review workflow, or an equivalent process.
106
104
  - Do not modify, create, delete, rename, stage, commit, or otherwise mutate files.
107
- - Do not execute commands, tests, builds, network calls, or any operation that can change state.
108
- - Read only the bounded review input and explicitly represented targeted supporting material.
109
- - Do not perform broad repository discovery or a full repository scan.
105
+ - Do not execute shell, Git, tests, builds, network calls, external processes, or any operation that can change state.
106
+ - Use read-only checkout access only for concrete module, caller, consumer, schema, type, configuration, fixture, test, or contract coupling.
107
+ - Do not perform unconditional repository discovery or a full repository scan.
110
108
  - Report only corrections supported by a concrete target and evidence.
111
109
  - Do not invent an issue merely to return feedback.
112
110
  - Return REVIEW_BLOCKED rather than delegate or provide an incomplete success result.
@@ -134,19 +132,22 @@ Return exactly one result using the Reviewer Output Protocol.
134
132
 
135
133
  ## Resume Prompt
136
134
 
137
- Resume the exact same Reviewer. Do not create a new Reviewer with this prompt.
135
+ Prefer native continuation of the same Reviewer. If unavailable, use this complete prompt for one compliant replacement under the replacement rules below.
138
136
 
139
137
  ```text
140
138
  Continue as the same independent Test Reviewer.
141
139
 
142
140
  Response:
143
141
  This is response [RESPONSE_NUMBER] of a maximum five responses.
144
- New review-input identity: [INPUT_ID]
145
- New manifest: [MANIFEST]
146
- Current change context: [CHANGE_CONTEXT]
142
+ Continuation mode: [NATIVE_OR_REPLACEMENT]
143
+ Artifact directory: [ARTIFACT_DIR]
144
+ Specification path and content: [SPEC_PATH_AND_CONTENT]
145
+ Plan path and content: [PLAN_PATH_AND_CONTENT]
146
+ Checkout root: [CHECKOUT_ROOT]
147
+ Requirement and clarifications: [REQUIREMENT_CONTEXT]
148
+ Current production-and-test change: [CURRENT_CHANGE]
147
149
  Current coverage context: [COVERAGE_CONTEXT]
148
- Current test context: [TEST_CONTEXT]
149
- Current supporting context: [SUPPORTING_CONTEXT]
150
+ Current test context and Main-Agent execution results: [TEST_CONTEXT]
150
151
  Previously accepted corrections: [ACCEPTED_CORRECTIONS_OR_NONE]
151
152
  Repairs actually made: [REPAIRS_OR_NONE]
152
153
  Main-Agent native verification: [VERIFICATION]
@@ -233,28 +234,25 @@ Independent review converges when:
233
234
  4. Validate the response against the output protocol and record it in the report.
234
235
  5. Adjudicate every correction and record accepted, rejected, or unresolved with evidence.
235
236
  6. For accepted corrections, the Main Agent repairs the current workspace, executes focused and required regressions, then performs a complete Main-Agent review round over the new state within its ten-round budget.
236
- 7. If responses remain, capture the complete fresh input and natively resume the exact same Reviewer. Never create a replacement.
237
- 8. If response five contains an accepted correction, do not repair and claim pass without independent recheck. Return `TEST_NEEDS_FIXES` with the known correction.
238
- 9. If response five contains an unresolved correction or `REVIEW_BLOCKED` that cannot be corrected safely, return `TEST_BLOCKED`.
239
- 10. Stop sending messages when the review converges, reaches its limit, or becomes blocked. Explicitly terminate the unit only when the host safely supports it.
237
+ 7. If responses remain, prepare the complete latest Test input and prefer native continuation. If unavailable or failed and no replacement has been used in this stage, create one compliant replacement only after the prior Reviewer is no longer active; reapply the full gate and continue with the next response number.
238
+ 8. A replacement receives complete current state and all prior adjudication, never only a conversation summary. Replacement does not reset the five-response budget. At most one replacement may be created in the stage, and only one Reviewer may be active.
239
+ 9. If response five contains an accepted correction, do not repair and claim pass without independent recheck. Return `TEST_NEEDS_FIXES` with the known correction.
240
+ 10. If response five contains an unresolved correction or `REVIEW_BLOCKED` that cannot be corrected safely, return `TEST_BLOCKED`.
241
+ 11. Stop sending messages when the review converges, reaches its limit, or becomes blocked. Explicitly terminate the unit only when the host safely supports it.
240
242
 
241
243
  ## Strict Downgrade
242
244
 
243
- Use Strict Downgrade when:
244
-
245
- - required capability is unavailable or unproven before creation;
246
- - creation fails before a valid response;
247
- - a required structural capability disappears;
248
- - exact native resume fails after mutation.
245
+ Use Strict Downgrade when a required capability is unavailable or unproven, creation fails before a valid response, prohibited capability appears, the prior Reviewer may still be active, or no compliant continuation/replacement can be established.
249
246
 
250
247
  Behavior:
251
248
 
252
- 1. Never create a weaker or replacement Reviewer.
253
- 2. Never create a fresh Reviewer with a summary; summary context is not native continuation.
254
- 3. Before creation or before any valid response, record the downgrade and preserve the converged mandatory Main-Agent review.
255
- 4. When resume fails after mutation, the old independent result no longer describes the final state. Rerun mandatory Main-Agent review over the mutation within its remaining budget, record the downgrade, and set `final_state_rechecked=false` for independent review.
256
- 5. A semantic `REVIEW_BLOCKED` caused by incomplete or inconsistent required input is not silently downgraded. Correct the input and resume the same Reviewer within budget, or return `TEST_BLOCKED`.
257
- 6. Strict Downgrade is visible reduced assurance, not a new testing result and not permission to weaken Main-Agent review or native test execution.
249
+ 1. Never create a weaker Reviewer whose prohibited capabilities remain available.
250
+ 2. Prefer native continuation, but permit a fresh compliant replacement with complete latest input and prior adjudication; a summary alone is insufficient.
251
+ 3. Before creation or before any valid response, record downgrade and preserve the converged mandatory Main-Agent review.
252
+ 4. Creation failure without a response does not consume response budget; a produced malformed response follows the existing response-budget rule.
253
+ 5. When continuation and compliant replacement both fail after mutation, rerun mandatory Main-Agent review over the mutation within remaining budget, record downgrade, and set `final_state_rechecked=false`.
254
+ 6. A semantic `REVIEW_BLOCKED` caused by incomplete required input is not silently downgraded. Correct input within remaining shared budget using native continuation or compliant replacement, or return `TEST_BLOCKED`.
255
+ 7. Strict Downgrade is visible reduced assurance, not a new testing result and not permission to weaken Main-Agent review or native execution.
258
256
 
259
257
  ## Report Recording
260
258
 
@@ -264,6 +262,7 @@ Record:
264
262
  - each response number `1..5`, input identity, result, and correction count;
265
263
  - every Main-Agent adjudication and its evidence;
266
264
  - actual repair and native verification;
267
- - whether the exact final state was independently rechecked.
265
+ - whether the exact final state was independently rechecked;
266
+ - capability basis, continuation mode (`native`, `replacement`, `mixed`, or `not-applicable`), and replacement count.
268
267
 
269
268
  The report remains the human-readable test artifact. Do not create a separate Reviewer state machine or evidence package.
@@ -78,8 +78,12 @@ Coverage metrics, when available: <diagnostic values or not collected>. Percenta
78
78
  ## Independent Test Review
79
79
 
80
80
  - Status: completed / downgraded / not_applicable
81
+ - Capability Gate: <ENABLED | DOWNGRADED | NOT_APPLICABLE>
82
+ - Capability Basis: <platform-neutral structural evidence or unavailable>
83
+ - Downgrade Reason: <reason or none>
81
84
  - Responses: <0..5>
82
- - Capability gate or downgrade reason: <reason or none>
85
+ - Continuation Mode: <native | replacement | mixed | not-applicable>
86
+ - Reviewer Replacements: <0..1>
83
87
  - Corrections and Main-Agent adjudication: <summary or none>
84
88
  - Exact final state independently rechecked: yes / no
85
89
 
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env python3
2
+ """Report whether staged source changes include Prizm documentation updates."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import subprocess
7
+
8
+ SOURCE_EXTENSIONS = (".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs", ".java")
9
+ PRIZM_DOCS_PREFIX = ".prizmkit/prizm-docs/"
10
+
11
+
12
+ def staged_files() -> list[str]:
13
+ result = subprocess.run(
14
+ ["git", "diff", "--cached", "--name-only"],
15
+ text=True,
16
+ encoding="utf-8",
17
+ errors="replace",
18
+ stdout=subprocess.PIPE,
19
+ stderr=subprocess.DEVNULL,
20
+ check=False,
21
+ )
22
+ if result.returncode != 0:
23
+ return []
24
+ return [path for path in result.stdout.splitlines() if path.strip()]
25
+
26
+
27
+ def main() -> int:
28
+ files = staged_files()
29
+ source_files = [path for path in files if path.endswith(SOURCE_EXTENSIONS)]
30
+ if not source_files:
31
+ return 0
32
+
33
+ prizm_docs = [path for path in files if path.startswith(PRIZM_DOCS_PREFIX)]
34
+ if prizm_docs:
35
+ print(
36
+ f"PRIZMKIT_DOC_STATUS: {len(source_files)} source file(s) staged | "
37
+ f".prizmkit/prizm-docs/ updated ({len(prizm_docs)} file(s))",
38
+ end="",
39
+ )
40
+ else:
41
+ print(
42
+ f"PRIZMKIT_DOC_STATUS: {len(source_files)} source file(s) staged | "
43
+ ".prizmkit/prizm-docs/ NOT UPDATED — update if this is a feature change",
44
+ end="",
45
+ )
46
+ return 0
47
+
48
+
49
+ if __name__ == "__main__":
50
+ raise SystemExit(main())
@@ -6,7 +6,7 @@
6
6
  "hooks": [
7
7
  {
8
8
  "type": "command",
9
- "command": "python3 -c \"import subprocess,sys; r=subprocess.run(['git','diff','--cached','--name-only'],text=True,encoding='utf-8',errors='replace',stdout=subprocess.PIPE,stderr=subprocess.DEVNULL); files=[p for p in r.stdout.splitlines() if p.strip()] if r.returncode==0 else []; src=[p for p in files if p.endswith(('.ts','.tsx','.js','.jsx','.py','.go','.rs','.java'))]; prizm=[p for p in files if p.startswith('.prizmkit/prizm-docs/')]; print(f'PRIZMKIT_DOC_STATUS: {len(src)} source file(s) staged | .prizmkit/prizm-docs/ updated ({len(prizm)} file(s))' if src and prizm else (f'PRIZMKIT_DOC_STATUS: {len(src)} source file(s) staged | .prizmkit/prizm-docs/ NOT UPDATED — update if this is a feature change' if src else ''), end='')\""
9
+ "command": "python3 .prizmkit/scripts/commit-intent-status.py"
10
10
  }
11
11
  ]
12
12
  }
@@ -17,7 +17,7 @@
17
17
  "hooks": [
18
18
  {
19
19
  "type": "command",
20
- "command": "python3 -c \"import os,re,subprocess,sys; data=os.environ.get('CLAUDE_TOOL_INPUT') or os.environ.get('CODEBUDDY_TOOL_INPUT') or ''; script='.prizmkit/scripts/diff-prizm-docs.py'; should=bool(re.search(r'git\\s+(commit|merge|rebase)', data)); res=subprocess.run([sys.executable, script],text=True,encoding='utf-8',errors='replace',stdout=subprocess.PIPE,stderr=subprocess.DEVNULL) if should and os.path.isfile(script) else None; drift=(res.stdout.strip() if res else ''); print('PRIZMKIT_DRIFT_DETECTED: prizm-docs structural differences found:\\n'+drift+'\\nPlease update affected .prizmkit/prizm-docs/ files.') if drift else None\""
20
+ "command": "python3 .prizmkit/scripts/post-command-prizm-drift.py"
21
21
  }
22
22
  ]
23
23
  }
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env python3
2
+ """Report Prizm documentation drift after relevant Git commands."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import os
7
+ import re
8
+ import subprocess
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ DRIFT_SCRIPT = Path(".prizmkit/scripts/diff-prizm-docs.py")
13
+ RELEVANT_GIT_COMMAND = re.compile(r"git\s+(commit|merge|rebase)")
14
+
15
+
16
+ def tool_input() -> str:
17
+ return os.environ.get("CLAUDE_TOOL_INPUT") or os.environ.get("CODEBUDDY_TOOL_INPUT") or ""
18
+
19
+
20
+ def main() -> int:
21
+ if not RELEVANT_GIT_COMMAND.search(tool_input()) or not DRIFT_SCRIPT.is_file():
22
+ return 0
23
+
24
+ result = subprocess.run(
25
+ [sys.executable, str(DRIFT_SCRIPT)],
26
+ text=True,
27
+ encoding="utf-8",
28
+ errors="replace",
29
+ stdout=subprocess.PIPE,
30
+ stderr=subprocess.DEVNULL,
31
+ check=False,
32
+ )
33
+ drift = result.stdout.strip()
34
+ if drift:
35
+ print(
36
+ "PRIZMKIT_DRIFT_DETECTED: prizm-docs structural differences found:\n"
37
+ f"{drift}\n"
38
+ "Please update affected .prizmkit/prizm-docs/ files."
39
+ )
40
+ return 0
41
+
42
+
43
+ if __name__ == "__main__":
44
+ raise SystemExit(main())
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prizmkit",
3
- "version": "1.1.142",
3
+ "version": "1.1.144",
4
4
  "description": "Create a new PrizmKit-powered project with clean initialization — no framework dev files, just what you need.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,4 +1,4 @@
1
- const BUILT_IN_AI_CLI_NAMES = new Set(['claude', 'cbc', 'codebuddy', 'codex']);
1
+ const BUILT_IN_AI_CLI_NAMES = new Set(['claude', 'cbc', 'codebuddy', 'codex', 'pi']);
2
2
 
3
3
  function asText(value) {
4
4
  return typeof value === 'string' ? value : '';
package/src/clean.js CHANGED
@@ -252,6 +252,10 @@ export async function runClean(directory, options = {}) {
252
252
  const codexSkillsDir = path.join(projectRoot, '.agents', 'skills');
253
253
  results.push(...await removeKnownEntries(codexSkillsDir, allSkillNames, dryRun));
254
254
 
255
+ // .pi/skills/ — Pi project skills
256
+ const piSkillsDir = path.join(projectRoot, '.pi', 'skills');
257
+ results.push(...await removeKnownEntries(piSkillsDir, allSkillNames, dryRun));
258
+
255
259
  // .claude/agents/ — retired PrizmKit named-agent files
256
260
  const claudeAgentsDir = path.join(projectRoot, '.claude', 'agents');
257
261
  results.push(...await removeKnownEntries(claudeAgentsDir, agentFiles, dryRun));
@@ -280,13 +284,18 @@ export async function runClean(directory, options = {}) {
280
284
  const legacyCodexRulesDir = path.join(projectRoot, '.codex', 'rules');
281
285
  results.push(...await removeKnownEntries(legacyCodexRulesDir, ruleFileNames.map(n => `${n}.md`), dryRun));
282
286
 
283
- for (const platform of ['codebuddy', 'claude', 'codex']) {
287
+ const piRulesDir = path.join(projectRoot, '.pi', 'rules');
288
+ results.push(...await removeKnownEntries(piRulesDir, ruleFileNames.map(n => `${n}.md`), dryRun));
289
+
290
+ for (const platform of ['codebuddy', 'claude', 'codex', 'pi']) {
284
291
  const memoryFile = projectMemoryFile(platform);
285
292
  const privateMemoryFile = privateProjectMemoryFile(platform);
286
- if (!memoryFile || !privateMemoryFile) continue;
287
- results.push(await cleanProjectMemoryImport(projectRoot, memoryFile, privateMemoryFile, dryRun));
293
+ if (!privateMemoryFile) continue;
294
+ if (memoryFile) {
295
+ results.push(await cleanProjectMemoryImport(projectRoot, memoryFile, privateMemoryFile, dryRun));
296
+ results.push(await cleanMarkedProjectMemory(projectRoot, memoryFile, dryRun));
297
+ }
288
298
  results.push(await cleanMarkedProjectMemory(projectRoot, privateMemoryFile, dryRun));
289
- results.push(await cleanMarkedProjectMemory(projectRoot, memoryFile, dryRun));
290
299
  }
291
300
 
292
301
  // ── .gitignore cleanup ────────────────────────────────────────────────────
package/src/config.js CHANGED
@@ -99,9 +99,11 @@ async function detectInstalledPlatforms(projectRoot) {
99
99
  const hasClaude = await hasPlatformFiles(path.join(projectRoot, '.claude', 'commands'));
100
100
  const hasCodeBuddy = await hasPlatformFiles(path.join(projectRoot, '.codebuddy', 'skills'));
101
101
  const hasCodex = await hasPlatformFiles(path.join(projectRoot, '.agents', 'skills'));
102
+ const hasPi = await hasPlatformFiles(path.join(projectRoot, '.pi', 'skills'));
103
+ const detected = [hasClaude && 'claude', hasCodeBuddy && 'codebuddy', hasCodex && 'codex', hasPi && 'pi'].filter(Boolean);
102
104
 
103
- if (hasClaude && hasCodeBuddy && hasCodex) return 'all';
104
- if (hasClaude && hasCodeBuddy) return 'both';
105
+ if (detected.length > 1) return detected.length === 2 && hasClaude && hasCodeBuddy ? 'both' : 'all';
106
+ if (hasPi) return 'pi';
105
107
  if (hasCodex) return 'codex';
106
108
  if (hasCodeBuddy) return 'codebuddy';
107
109
  if (hasClaude) return 'claude';
@@ -121,18 +123,25 @@ function logManagedMemoryWarnings(mainFile, warnings) {
121
123
  async function removeProjectMemoryArtifacts(platform, projectRoot, dryRun) {
122
124
  const mainFile = projectMemoryFile(platform);
123
125
  const privateFile = privateProjectMemoryFile(platform);
124
- if (!mainFile || !privateFile) return;
126
+ if (!privateFile) return;
125
127
 
126
128
  const privatePath = path.join(projectRoot, privateFile);
127
129
  if (await fs.pathExists(privatePath)) {
130
+ const currentPrivate = await fs.readFile(privatePath, 'utf-8');
131
+ const normalizedPrivate = normalizeMainProjectMemoryContent(currentPrivate, null);
132
+ const remainingPrivate = normalizedPrivate.content.trim();
128
133
  if (dryRun) {
129
- console.log(chalk.gray(` [dry-run] remove ${privateFile}`));
134
+ console.log(chalk.gray(` [dry-run] update ${privateFile} (remove PrizmKit block)`));
135
+ } else if (remainingPrivate) {
136
+ await fs.writeFile(privatePath, `${remainingPrivate}\n`, 'utf-8');
137
+ console.log(chalk.green(` ✓ updated ${privateFile} (removed PrizmKit block)`));
130
138
  } else {
131
139
  await fs.remove(privatePath);
132
140
  console.log(chalk.red(` ✗ removed ${privateFile}`));
133
141
  }
134
142
  }
135
143
 
144
+ if (!mainFile) return;
136
145
  const mainPath = path.join(projectRoot, mainFile);
137
146
  if (!await fs.pathExists(mainPath)) return;
138
147
 
@@ -252,10 +261,21 @@ async function removePlatformFiles(platform, projectRoot, manifest, dryRun) {
252
261
  } else {
253
262
  const settingsFile = platform === 'claude'
254
263
  ? path.join(projectRoot, '.claude', 'settings.json')
255
- : path.join(projectRoot, '.codebuddy', 'settings.json');
264
+ : platform === 'pi'
265
+ ? path.join(projectRoot, '.pi', 'settings.json')
266
+ : path.join(projectRoot, '.codebuddy', 'settings.json');
256
267
  if (await fs.pathExists(settingsFile)) {
257
268
  if (dryRun) {
258
269
  console.log(chalk.gray(` [dry-run] remove ${path.relative(projectRoot, settingsFile)}`));
270
+ } else if (platform === 'pi') {
271
+ let settings = {};
272
+ try { settings = await fs.readJSON(settingsFile); } catch { settings = null; }
273
+ if (settings && !Array.isArray(settings) && typeof settings === 'object') {
274
+ delete settings.enableSkillCommands;
275
+ if (Object.keys(settings).length) await fs.writeJSON(settingsFile, settings, { spaces: 2 });
276
+ else await fs.remove(settingsFile);
277
+ console.log(chalk.green(' ✓ removed PrizmKit Pi setting'));
278
+ }
259
279
  } else {
260
280
  await fs.remove(settingsFile);
261
281
  console.log(chalk.red(` ✗ removed ${path.relative(projectRoot, settingsFile)}`));
@@ -353,6 +373,13 @@ async function removePlatformFiles(platform, projectRoot, manifest, dryRun) {
353
373
  }
354
374
  }
355
375
  }
376
+ } else if (platform === 'pi') {
377
+ for (const relativeDir of ['.pi/skills', '.pi/rules', '.pi']) {
378
+ const dir = path.join(projectRoot, relativeDir);
379
+ if (await fs.pathExists(dir) && !dryRun && (await fs.readdir(dir)).length === 0) {
380
+ await fs.remove(dir);
381
+ }
382
+ }
356
383
  }
357
384
  }
358
385
 
@@ -9,7 +9,7 @@ import os from 'os';
9
9
  /**
10
10
  * 允许检测的命令白名单(仅用于 which 检测)
11
11
  */
12
- const ALLOWED_COMMANDS = ['cbc', 'claude', 'codex'];
12
+ const ALLOWED_COMMANDS = ['cbc', 'claude', 'codex', 'pi'];
13
13
 
14
14
  /**
15
15
  * 检查命令是否存在于 PATH 中
@@ -33,30 +33,34 @@ function commandExists(cmd) {
33
33
  * 注意:platform 控制目录结构(.codebuddy/ 或 .claude/),
34
34
  * suggestedCli 是实际运行的可执行命令,二者相互独立。
35
35
  *
36
- * @returns {{ cbc: boolean, claude: boolean, codex: boolean, suggested: string, suggestedCli: string }}
36
+ * @returns {{ cbc: boolean, claude: boolean, codex: boolean, pi: boolean, suggested: string, suggestedCli: string }}
37
37
  */
38
38
  export function detectPlatform() {
39
39
  const cbc = commandExists('cbc');
40
40
  const claude = commandExists('claude');
41
41
  const codex = commandExists('codex');
42
+ const pi = commandExists('pi');
42
43
 
43
- // platform 建议:基于目录结构
44
+ // Preserve historical recommendations when Pi is absent.
44
45
  let suggested = 'codebuddy';
45
- if (cbc && claude && codex) {
46
+ if (pi) {
47
+ suggested = (cbc || claude || codex) ? 'all' : 'pi';
48
+ } else if (cbc && claude && codex) {
46
49
  suggested = 'all';
47
50
  } else if (codex && !cbc && !claude) {
48
51
  suggested = 'codex';
49
- } else if ((claude) && !cbc) {
52
+ } else if (claude && !cbc) {
50
53
  suggested = 'claude';
51
54
  } else if (cbc && !claude) {
52
55
  suggested = 'codebuddy';
53
56
  }
54
57
 
55
- // CLI 命令建议:优先 cbc,其次 claude,再次 codex
58
+ // Preserve historical precedence and use Pi when it is the first available CLI.
56
59
  let suggestedCli = '';
57
60
  if (cbc) suggestedCli = 'cbc';
58
61
  else if (claude) suggestedCli = 'claude';
59
62
  else if (codex) suggestedCli = 'codex';
63
+ else if (pi) suggestedCli = 'pi';
60
64
 
61
- return { cbc, claude, codex, suggested, suggestedCli };
65
+ return { cbc, claude, codex, pi, suggested, suggestedCli };
62
66
  }
package/src/index.js CHANGED
@@ -52,6 +52,7 @@ export async function runScaffold(directory, options) {
52
52
  detected.cbc ? chalk.green('cbc ✓') : chalk.gray('cbc ✗'),
53
53
  detected.claude ? chalk.green('claude ✓') : chalk.gray('claude ✗'),
54
54
  detected.codex ? chalk.green('codex ✓') : chalk.gray('codex ✗'),
55
+ detected.pi ? chalk.green('pi ✓') : chalk.gray('pi ✗'),
55
56
  ].join(' ');
56
57
  console.log(` 检测到的 CLI 工具: ${cliStatus}`);
57
58
  console.log(` 目标目录: ${projectRoot}`);
@@ -108,6 +109,10 @@ export async function runScaffold(directory, options) {
108
109
  name: `Codex (AGENTS.md + .agents/skills + .codex/)${detected.codex ? chalk.green(' ← 已检测到 codex') : ''}`,
109
110
  value: 'codex',
110
111
  },
112
+ {
113
+ name: `Pi (.pi/skills + .pi/APPEND_SYSTEM.md)${detected.pi ? chalk.green(' ← 已检测到 pi') : ''}`,
114
+ value: 'pi',
115
+ },
111
116
  {
112
117
  name: '同时安装全部平台',
113
118
  value: 'all',
package/src/platforms.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { isBuiltInAiCli } from './ai-cli-launch.js';
2
2
 
3
- export const PLATFORM_IDS = ['codebuddy', 'claude', 'codex'];
3
+ export const PLATFORM_IDS = ['codebuddy', 'claude', 'codex', 'pi'];
4
4
  export const PLATFORM_GROUPS = {
5
5
  all: PLATFORM_IDS,
6
6
  };
@@ -23,6 +23,7 @@ export function platformFromAiCli(aiCli) {
23
23
  const name = token.split(/[\\/]/).pop()?.toLowerCase() || '';
24
24
  if (name === 'claude') return 'claude';
25
25
  if (name === 'codex') return 'codex';
26
+ if (name === 'pi') return 'pi';
26
27
  if (name === 'cbc' || name === 'codebuddy') return 'codebuddy';
27
28
  return null;
28
29
  }
@@ -48,10 +49,11 @@ export function isKnownPlatform(platform) {
48
49
 
49
50
  export function platformLabel(platform) {
50
51
  if (platform === 'both') return 'legacy codebuddy/claude group';
51
- if (platform === 'all') return 'All platforms (CodeBuddy, Claude Code, Codex)';
52
+ if (platform === 'all') return 'All platforms (CodeBuddy, Claude Code, Codex, Pi)';
52
53
  if (platform === 'codebuddy') return 'CodeBuddy';
53
54
  if (platform === 'claude') return 'Claude Code';
54
55
  if (platform === 'codex') return 'Codex';
56
+ if (platform === 'pi') return 'Pi';
55
57
  return platform;
56
58
  }
57
59
 
@@ -63,6 +65,7 @@ export function projectMemoryFile(platform) {
63
65
  }
64
66
 
65
67
  export function privateProjectMemoryFile(platform) {
68
+ if (platform === 'pi') return '.pi/APPEND_SYSTEM.md';
66
69
  const mainFile = projectMemoryFile(platform);
67
70
  if (!mainFile) return null;
68
71
  return mainFile.replace(/\.md$/i, '.private.md');
@@ -80,5 +83,6 @@ export function defaultCliForPlatform(platform) {
80
83
  if (platform === 'claude') return 'claude';
81
84
  if (platform === 'codebuddy') return 'cbc';
82
85
  if (platform === 'codex') return 'codex';
86
+ if (platform === 'pi') return 'pi';
83
87
  return '';
84
88
  }