prizmkit 1.1.159 → 1.1.160

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 (77) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/adapters/codebuddy/settings-adapter.js +76 -25
  3. package/bundled/dev-pipeline/prizmkit_runtime/gitops.py +9 -2
  4. package/bundled/dev-pipeline/prizmkit_runtime/runtime_commit.py +42 -15
  5. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +1 -1
  6. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +1 -1
  7. package/bundled/dev-pipeline/templates/sections/runtime-commit-handoff.md +6 -6
  8. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +4 -3
  9. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +2 -2
  10. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +2 -2
  11. package/bundled/dev-pipeline/tests/test_runtime_commit.py +68 -13
  12. package/bundled/dev-pipeline/tests/test_unified_cli.py +10 -6
  13. package/bundled/rules/prizm/prizm-commit-workflow.md +7 -7
  14. package/bundled/rules/prizm/prizm-documentation.md +24 -65
  15. package/bundled/rules/prizm/prizm-progressive-loading.md +9 -8
  16. package/bundled/skills/_metadata.json +1 -1
  17. package/bundled/skills/prizmkit-code-review/SKILL.md +1 -1
  18. package/bundled/skills/prizmkit-committer/SKILL.md +48 -41
  19. package/bundled/skills/prizmkit-deploy/SKILL.md +172 -198
  20. package/bundled/skills/prizmkit-deploy/references/capability-contract.md +137 -0
  21. package/bundled/skills/prizmkit-deploy/references/deploy-record-schema.json +1053 -0
  22. package/bundled/skills/prizmkit-deploy/references/deployment-declaration-schema.json +291 -0
  23. package/bundled/skills/prizmkit-deploy/references/legacy-migration.md +90 -0
  24. package/bundled/skills/prizmkit-deploy/references/policy-and-execution.md +170 -0
  25. package/bundled/skills/prizmkit-deploy/references/real-resource-test-policy.md +73 -0
  26. package/bundled/skills/prizmkit-deploy/references/verification-and-recovery.md +164 -0
  27. package/bundled/skills/prizmkit-deploy/scripts/validate-deploy-artifact.py +371 -0
  28. package/bundled/skills/prizmkit-implement/SKILL.md +2 -2
  29. package/bundled/skills/prizmkit-init/SKILL.md +17 -17
  30. package/bundled/skills/prizmkit-init/references/update-supplement.md +5 -5
  31. package/bundled/skills/prizmkit-prizm-docs/SKILL.md +17 -11
  32. package/bundled/skills/prizmkit-prizm-docs/assets/prizm-docs-format.md +95 -155
  33. package/bundled/skills/prizmkit-prizm-docs/references/op-init.md +6 -5
  34. package/bundled/skills/prizmkit-prizm-docs/references/op-rebuild.md +4 -2
  35. package/bundled/skills/prizmkit-prizm-docs/references/op-status.md +9 -10
  36. package/bundled/skills/prizmkit-prizm-docs/references/op-update.md +6 -5
  37. package/bundled/skills/prizmkit-prizm-docs/references/op-validate.md +13 -12
  38. package/bundled/skills/prizmkit-retrospective/SKILL.md +3 -3
  39. package/bundled/skills/prizmkit-test/SKILL.md +26 -13
  40. package/bundled/skills/prizmkit-test/references/boundary-contract-and-test-double-guidance.md +8 -4
  41. package/bundled/skills/prizmkit-test/references/independent-test-review.md +7 -3
  42. package/bundled/skills/prizmkit-test/references/test-coverage-model.md +33 -10
  43. package/bundled/skills/prizmkit-test/references/test-report-template.md +33 -11
  44. package/bundled/skills/prizmkit-workflow/SKILL.md +5 -7
  45. package/bundled/skills/prizmkit-workflow/references/workflow-state-protocol.md +5 -6
  46. package/bundled/templates/hooks/commit-intent-status.py +9 -41
  47. package/bundled/templates/hooks/commit-intent.json +3 -3
  48. package/bundled/templates/hooks/diff-prizm-docs.py +3 -1
  49. package/bundled/templates/hooks/prizm-pre-commit.py +32 -6
  50. package/bundled/templates/hooks/run-python-hook.cjs +44 -0
  51. package/bundled/templates/hooks/validate-prizm-docs.py +399 -173
  52. package/bundled/templates/project-memory-template.md +19 -19
  53. package/package.json +1 -1
  54. package/src/clean.js +6 -7
  55. package/src/gitignore-template.js +8 -12
  56. package/src/scaffold.js +125 -71
  57. package/bundled/skills/prizmkit-deploy/assets/deploy-document-template.md +0 -89
  58. package/bundled/skills/prizmkit-deploy/references/ci-cd-workflows.md +0 -145
  59. package/bundled/skills/prizmkit-deploy/references/cloud-platform-deploy.md +0 -109
  60. package/bundled/skills/prizmkit-deploy/references/data-safety-examples.md +0 -120
  61. package/bundled/skills/prizmkit-deploy/references/database-setup.md +0 -65
  62. package/bundled/skills/prizmkit-deploy/references/deploy-config-schema.md +0 -150
  63. package/bundled/skills/prizmkit-deploy/references/deploy-history-schema.md +0 -78
  64. package/bundled/skills/prizmkit-deploy/references/deploy-metadata-schema.json +0 -49
  65. package/bundled/skills/prizmkit-deploy/references/deployment-modes.md +0 -50
  66. package/bundled/skills/prizmkit-deploy/references/direct-upload.md +0 -26
  67. package/bundled/skills/prizmkit-deploy/references/dns-setup.md +0 -42
  68. package/bundled/skills/prizmkit-deploy/references/docker-deploy.md +0 -31
  69. package/bundled/skills/prizmkit-deploy/references/firewall-setup.md +0 -37
  70. package/bundled/skills/prizmkit-deploy/references/live-validation-notes.md +0 -21
  71. package/bundled/skills/prizmkit-deploy/references/nginx-blue-green.md +0 -59
  72. package/bundled/skills/prizmkit-deploy/references/pending-input-schema.json +0 -164
  73. package/bundled/skills/prizmkit-deploy/references/ssh-adapter-flow.md +0 -221
  74. package/bundled/skills/prizmkit-deploy/references/ssh-bootstrap-flow.md +0 -49
  75. package/bundled/skills/prizmkit-deploy/references/ssh-execution-flow.md +0 -41
  76. package/bundled/skills/prizmkit-deploy/references/ssh-takeover.md +0 -20
  77. package/bundled/skills/prizmkit-deploy/references/ssl-setup.md +0 -56
@@ -4,30 +4,30 @@
4
4
  This project uses PrizmKit with the Prizm documentation system for AI-optimized progressive context loading.
5
5
 
6
6
  ### Progressive Loading Protocol
7
- - ON SESSION START: Always read `.prizmkit/prizm-docs/root.prizm` first (L0 project map)
8
- - ON RESUME (feature/bugfix directory has session-summary.md): Read session-summary.md first for prior context, then load only L1/L2 for modules mentioned in it
9
- - ON TASK: Read L1 (`.prizmkit/prizm-docs/<module>.prizm`) for relevant modules referenced in MODULE_INDEX or MODULE_GROUPS. If entries have keyword tags (e.g., `[login, jwt, oauth]`), match user's task against tags to prioritize which modules to load.
10
- - ON FILE EDIT: Read L2 (`.prizmkit/prizm-docs/<module>/<submodule>.prizm`) before modifying files. Pay attention to TRAPS and DECISIONS.
11
- - NEVER load all .prizm docs at once. Load only what is needed for the current task.
12
-
13
- ### Auto-Update Protocol
14
- - BEFORE EVERY COMMIT: Update affected `.prizmkit/prizm-docs/` files
15
- - Platform hooks (rules or UserPromptSubmit) will remind you automatically
16
- - Use `/prizmkit-committer` for the complete commit workflow
7
+ - ON SESSION START: Read exact `.prizmkit/prizm-docs/root.prizm` first when it exists; it alone is L0.
8
+ - ON TASK: Read only relevant direct-child L1 documents (`.prizmkit/prizm-docs/<module>.prizm`) selected through MODULE_INDEX or MODULE_GROUPS pointers.
9
+ - ON FILE EDIT: Read the complete relevant nested L2 and complete parent/child documents needed to resolve its SUBDIRS or DETAILS pointer. Grep-only fragments are insufficient modification context.
10
+ - IF L2 IS ABSENT: Inspect bounded target source and narrowly implicated contracts without creating a placeholder; documentation creation belongs to Retrospective or explicit repair.
11
+ - NEVER load all `.prizm` docs at once, load unrelated modules, or create L3.
12
+
13
+ ### Framework Directory and Maintenance Protocol
14
+ - `.prizmkit/**` is the PrizmKit framework directory; owning capabilities may read/write their exact managed artifacts.
15
+ - The project alone controls whether framework paths are ignored, untracked, or tracked. Do not add, remove, force-add, stage, commit, or interpret `.gitignore` entries as documentation maintenance.
16
+ - Run `/prizmkit-retrospective` after validated changes when structure, interfaces, dependencies, behavior, or durable knowledge changed; it never owns staging/commit.
17
+ - Use `/prizmkit-committer` for an exact commit operation. A Git-visible `.prizmkit/**` intended path receives ordinary exact-manifest and Secret checks; ignored paths remain absent.
17
18
 
18
19
  ### Doc Format Rules
19
20
  - All `.prizm` files use KEY: value format, not prose
20
- - Size limits: L0 = 4KB, L1 = 4KB, L2 = 5KB
21
+ - Path levels and raw UTF-8 limits: exact `root.prizm` = L0/4096B; other direct `.prizm` children = L1/4096B; nested `.prizm` documents = L2/5120B
21
22
  - Arrow notation (->) indicates load pointers to other .prizm docs
22
23
  - Memory files must not contain CHANGELOG sections/files, UPDATED/date metadata, feature/bug/refactor/task/session/run/pipeline/workflow IDs, branch names, absolute worktree paths, or `.prizmkit/specs` / `.prizmkit/dev-pipeline` artifact paths
23
- - Update durable sections in place; git history is the change log
24
- - No date/time fields git is the authoritative source for temporal info
25
-
26
- ### Creating New L2 Docs
27
- - When you first modify files in a sub-module that has no L2 doc:
28
- 1. Read the source files in that sub-module
29
- 2. Generate a new L2 `.prizm` file following Prizm specification
30
- 3. Add a pointer in the parent L1 doc's SUBDIRS section
24
+ - Update durable sections in place; temporal and workflow history stays outside Prizm memory
25
+ - No date/time fields and no Git-history requirement
26
+
27
+ ### Missing L2 Fallback
28
+ - When relevant L2 is absent, inspect only bounded source needed for the current modification.
29
+ - Do not create a placeholder merely because source was read or changed.
30
+ - Retrospective or explicit documentation repair may later write a complete Value-Gate-qualified L2 and resolving parent pointer after identity, ownership, capacity, and format checks pass.
31
31
 
32
32
  ### Available Commands
33
33
  Run `/prizmkit` to see all available PrizmKit commands.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prizmkit",
3
- "version": "1.1.159",
3
+ "version": "1.1.160",
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": {
package/src/clean.js CHANGED
@@ -5,13 +5,12 @@ import { loadMetadata, loadRulesMetadata } from './metadata.js';
5
5
  import { readManifest } from './manifest.js';
6
6
  import { privateProjectMemoryFile, projectMemoryFile } from './platforms.js';
7
7
 
8
+ // Cleanup owns only deterministic private host-memory entries. Existing
9
+ // `.prizmkit/**` ignore policy is project-owned and must remain untouched.
8
10
  const PRIZMKIT_GITIGNORE_HEADERS = new Set([
9
- '# PrizmKit',
10
- '# PrizmKit 运行时全部产物(state/、prizm-docs/、.env、logs 等)',
11
+ '# PrizmKit private host memory',
11
12
  ]);
12
13
  const PRIZMKIT_GITIGNORE_ENTRIES = new Set([
13
- '.prizmkit/',
14
- '.prizmkit/*',
15
14
  'CLAUDE.private.md',
16
15
  'AGENTS.private.md',
17
16
  'CODEBUDDY.private.md',
@@ -298,7 +297,7 @@ export async function runClean(directory, options = {}) {
298
297
  results.push(await cleanMarkedProjectMemory(projectRoot, privateMemoryFile, dryRun));
299
298
  }
300
299
 
301
- // ── .gitignore cleanup ────────────────────────────────────────────────────
300
+ // ── Private host-memory .gitignore cleanup (never `.prizmkit/**`) ─────────
302
301
  const gitignoreResult = await cleanGitignore(projectRoot, dryRun);
303
302
 
304
303
  // ── Global team (optional) ────────────────────────────────────────────────
@@ -326,9 +325,9 @@ export async function runClean(directory, options = {}) {
326
325
 
327
326
  if (gitignoreResult.updated) {
328
327
  if (gitignoreResult.reason === 'dry_run') {
329
- console.log(chalk.gray(' [dry-run] 将更新 .gitignore(移除 PrizmKit 段)'));
328
+ console.log(chalk.gray(' [dry-run] 将更新 .gitignore(仅移除私有主机记忆条目)'));
330
329
  } else {
331
- console.log(chalk.green(' ✓ 已更新 .gitignore(移除 PrizmKit 段)'));
330
+ console.log(chalk.green(' ✓ 已更新 .gitignore(仅移除私有主机记忆条目)'));
332
331
  }
333
332
  }
334
333
 
@@ -1,22 +1,18 @@
1
1
  /**
2
- * .gitignore 模板
3
- * 为新创建的 PrizmKit 项目生成合适的 .gitignore 内容。
2
+ * Generate only explicit private host-memory ignores.
3
+ *
4
+ * `.prizmkit/**` tracking policy is project-controlled: this module never adds,
5
+ * removes, or interprets framework-directory ignore entries.
4
6
  */
5
7
 
6
8
  /**
7
- * 生成 .gitignore 内容
8
- * @param {Object} options - { pipeline: boolean }
9
+ * @param {Object} options - { privateMemoryFiles?: string[] }
9
10
  * @returns {string}
10
11
  */
11
12
  export function generateGitignore(options = {}) {
12
13
  const privateMemoryFiles = Array.isArray(options.privateMemoryFiles)
13
- ? options.privateMemoryFiles.filter(Boolean)
14
+ ? [...new Set(options.privateMemoryFiles.filter(Boolean))]
14
15
  : [];
15
- const lines = [
16
- '# PrizmKit',
17
- '.prizmkit/*',
18
- ...privateMemoryFiles,
19
- ];
20
-
21
- return [...new Set(lines)].join('\n');
16
+ if (privateMemoryFiles.length === 0) return '';
17
+ return ['# PrizmKit private host memory', ...privateMemoryFiles].join('\n');
22
18
  }
package/src/scaffold.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * 全部使用 Copy 模式(不使用 symlink),项目完全独立于框架仓库。
5
5
  * 通过 adapters 转换 core/ 资源为平台特定格式。
6
6
  * 不安装框架开发文件(docs、tests、scripts 等)。
7
- * 自动生成 .gitignore。
7
+ * 仅按显式平台需要合并私有记忆文件 ignore;`.prizmkit/**` Git 策略由项目控制。
8
8
  */
9
9
 
10
10
  import chalk from 'chalk';
@@ -381,6 +381,57 @@ export async function installSkills(platform, skills, projectRoot, dryRun, runti
381
381
  * Skill-owned references now provide inline reviewer/implementation subagent contracts.
382
382
  */
383
383
 
384
+ const MANAGED_HOOK_MARKERS = [
385
+ '.prizmkit/scripts/commit-intent-status.py',
386
+ '.prizmkit/scripts/post-command-prizm-drift.py',
387
+ 'PRIZMKIT_DOC_UPDATE_REQUIRED',
388
+ 'PrizmKit: .prizmkit/prizm-docs found',
389
+ ];
390
+
391
+ function isManagedHookEntry(entry) {
392
+ let serialized = '';
393
+ try {
394
+ serialized = JSON.stringify(entry);
395
+ } catch {
396
+ return false;
397
+ }
398
+ return MANAGED_HOOK_MARKERS.some(marker => serialized.includes(marker));
399
+ }
400
+
401
+ export function mergeManagedHooks(existingHooks = {}, managedHooks = {}) {
402
+ if (!existingHooks || typeof existingHooks !== 'object' || Array.isArray(existingHooks)) {
403
+ throw new Error('Cannot preserve malformed user hooks: hooks must be an object');
404
+ }
405
+ const merged = {};
406
+ for (const [event, entries] of Object.entries(existingHooks)) {
407
+ if (!Array.isArray(entries)) {
408
+ throw new Error(`Cannot preserve malformed user hooks: ${event} must be an array`);
409
+ }
410
+ merged[event] = entries.filter(entry => !isManagedHookEntry(entry));
411
+ }
412
+ for (const [event, entries] of Object.entries(managedHooks || {})) {
413
+ if (!Array.isArray(entries)) {
414
+ throw new Error(`Managed hook template is invalid: ${event} must be an array`);
415
+ }
416
+ const preserved = Array.isArray(merged[event]) ? merged[event] : [];
417
+ merged[event] = [...preserved, ...entries];
418
+ }
419
+ return merged;
420
+ }
421
+
422
+ async function readUserSettings(settingsPath) {
423
+ if (!await fs.pathExists(settingsPath)) return {};
424
+ try {
425
+ const value = await fs.readJSON(settingsPath);
426
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
427
+ throw new Error('settings root must be an object');
428
+ }
429
+ return value;
430
+ } catch (error) {
431
+ throw new Error(`Cannot preserve malformed user settings at ${settingsPath}: ${error.message}`);
432
+ }
433
+ }
434
+
384
435
  /**
385
436
  * 安装平台配置文件(settings/hooks/rules)
386
437
  */
@@ -412,11 +463,16 @@ export async function installSettings(platform, projectRoot, options, dryRun, ru
412
463
 
413
464
  await fs.ensureDir(path.dirname(settingsPath));
414
465
 
415
- // Read hook definition from unified template.
466
+ // Replace only deterministic managed hook entries and preserve user settings/hooks.
416
467
  const templatesDir = getTemplatesDir();
417
468
  const hookTemplate = await fs.readJSON(path.join(templatesDir, 'hooks', 'commit-intent.json'));
469
+ const existingSettings = await readUserSettings(settingsPath);
470
+ const settings = {
471
+ ...existingSettings,
472
+ hooks: mergeManagedHooks(existingSettings.hooks, hookTemplate.hooks),
473
+ };
418
474
 
419
- await fs.writeFile(settingsPath, JSON.stringify(hookTemplate, null, 2));
475
+ await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2));
420
476
  console.log(chalk.green(` ✓ .codebuddy/settings.json`));
421
477
 
422
478
  // Install rules for CodeBuddy
@@ -466,26 +522,37 @@ export async function installSettings(platform, projectRoot, options, dryRun, ru
466
522
  'Bash(jq *)',
467
523
  ];
468
524
 
525
+ const settingsPath = path.join(claudeDir, 'settings.json');
526
+ const existingSettings = await readUserSettings(settingsPath);
527
+ const existingPermissions = existingSettings.permissions && typeof existingSettings.permissions === 'object'
528
+ ? existingSettings.permissions
529
+ : {};
530
+ const mergedPermissions = [...new Set([
531
+ ...(Array.isArray(existingPermissions.allow) ? existingPermissions.allow : []),
532
+ ...permissions,
533
+ ])];
534
+ const managedHooks = {
535
+ ...hookTemplate.hooks,
536
+ SessionStart: [
537
+ {
538
+ matcher: 'startup',
539
+ hooks: [
540
+ {
541
+ type: 'command',
542
+ command: 'test -f .prizmkit/prizm-docs/root.prizm && echo "PrizmKit: .prizmkit/prizm-docs found. Read exact root.prizm for project context." || echo "PrizmKit: No .prizmkit/prizm-docs found. Run /prizmkit-init to bootstrap."',
543
+ },
544
+ ],
545
+ },
546
+ ],
547
+ };
469
548
  const settings = {
470
- permissions: { allow: permissions },
471
- hooks: {
472
- ...hookTemplate.hooks,
473
- SessionStart: [
474
- {
475
- matcher: 'startup',
476
- hooks: [
477
- {
478
- type: 'command',
479
- command: 'test -f .prizmkit/prizm-docs/root.prizm && echo "PrizmKit: .prizmkit/prizm-docs found. Read root.prizm for project context." || echo "PrizmKit: No .prizmkit/prizm-docs found. Run /prizmkit-init to bootstrap."',
480
- },
481
- ],
482
- },
483
- ],
484
- },
549
+ ...existingSettings,
550
+ permissions: { ...existingPermissions, allow: mergedPermissions },
551
+ hooks: mergeManagedHooks(existingSettings.hooks, managedHooks),
485
552
  };
486
553
 
487
554
  await fs.writeFile(
488
- path.join(claudeDir, 'settings.json'),
555
+ settingsPath,
489
556
  JSON.stringify(settings, null, 2)
490
557
  );
491
558
  console.log(chalk.green(` ✓ .claude/settings.json`));
@@ -553,59 +620,33 @@ function escapeRegExp(value) {
553
620
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
554
621
  }
555
622
 
556
- function ensureTrailingNewline(value) {
557
- return value.endsWith('\n') ? value : `${value}\n`;
558
- }
559
-
560
- function buildStandalonePrizmHook(templateContent) {
561
- const content = templateContent.trimEnd();
562
- const firstNewline = content.indexOf('\n');
563
-
564
- if (content.startsWith('#!') && firstNewline !== -1) {
565
- return [
566
- content.slice(0, firstNewline),
567
- PRIZMKIT_HOOK_MARKER_START,
568
- content.slice(firstNewline + 1),
569
- PRIZMKIT_HOOK_MARKER_END,
570
- '',
571
- ].join('\n');
572
- }
573
-
574
- return [
575
- PRIZMKIT_HOOK_MARKER_START,
576
- content,
577
- PRIZMKIT_HOOK_MARKER_END,
578
- '',
579
- ].join('\n');
580
- }
581
-
582
623
  function buildShellPythonLauncherHook() {
583
624
  return [
584
625
  PRIZMKIT_HOOK_MARKER_START,
585
- '# PrizmKit Python validation launcher',
586
- 'PRIZMKIT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || PRIZMKIT_ROOT=""',
587
- 'if [ -n "$PRIZMKIT_ROOT" ] && [ -f "$PRIZMKIT_ROOT/.prizmkit/prizm-docs/root.prizm" ]; then',
588
- ' PRIZMKIT_VALIDATE="$PRIZMKIT_ROOT/.prizmkit/scripts/validate-prizm-docs.py"',
589
- ' [ -f "$PRIZMKIT_VALIDATE" ] || PRIZMKIT_VALIDATE="$PRIZMKIT_ROOT/.prizmkit/dev-pipeline/scripts/validate-prizm-docs.py"',
590
- ' if [ -f "$PRIZMKIT_VALIDATE" ]; then',
591
- ' if command -v python3 >/dev/null 2>&1; then',
592
- ' python3 "$PRIZMKIT_VALIDATE" --staged || exit $?',
593
- ' elif command -v python >/dev/null 2>&1; then',
594
- ' python "$PRIZMKIT_VALIDATE" --staged || exit $?',
595
- ' elif command -v py >/dev/null 2>&1; then',
596
- ' py -3 "$PRIZMKIT_VALIDATE" --staged || exit $?',
597
- ' else',
598
- ' echo "PrizmKit: Python 3 is required for .prizm docs validation." >&2',
599
- ' exit 1',
600
- ' fi',
601
- ' fi',
626
+ '# PrizmKit host-neutral staged documentation validation',
627
+ 'PRIZMKIT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0',
628
+ 'PRIZMKIT_RUNNER="$PRIZMKIT_ROOT/.prizmkit/scripts/run-python-hook.cjs"',
629
+ 'PRIZMKIT_PRE_COMMIT="$PRIZMKIT_ROOT/.prizmkit/scripts/prizm-pre-commit.py"',
630
+ 'if [ -f "$PRIZMKIT_RUNNER" ] && [ -f "$PRIZMKIT_PRE_COMMIT" ] && command -v node >/dev/null 2>&1; then',
631
+ ' node "$PRIZMKIT_RUNNER" "$PRIZMKIT_PRE_COMMIT" || exit $?',
632
+ 'elif git diff --cached --quiet -- .prizmkit/prizm-docs; then',
633
+ ' :',
634
+ 'else',
635
+ ' echo "PrizmKit: staged Prizm documents require the installed host-neutral validator launcher." >&2',
636
+ ' exit 1',
602
637
  'fi',
603
638
  PRIZMKIT_HOOK_MARKER_END,
604
639
  '',
605
640
  ].join('\n');
606
641
  }
607
642
 
643
+ function buildStandalonePrizmHook(templateContent) {
644
+ void templateContent;
645
+ return `#!/bin/sh\n${buildShellPythonLauncherHook()}`;
646
+ }
647
+
608
648
  function buildAppendedPrizmHook(templateContent, userHookContent) {
649
+ void templateContent;
609
650
  if (/^#!.*\bpython[0-9.]*\b/i.test(userHookContent)) {
610
651
  return [
611
652
  PRIZMKIT_HOOK_MARKER_START,
@@ -613,13 +654,15 @@ function buildAppendedPrizmHook(templateContent, userHookContent) {
613
654
  'prizmkit_root_result = subprocess.run(["git", "rev-parse", "--show-toplevel"], text=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, check=False)',
614
655
  'if prizmkit_root_result.returncode == 0:',
615
656
  ' prizmkit_root = pathlib.Path(prizmkit_root_result.stdout.strip())',
616
- ' prizmkit_validate = prizmkit_root / ".prizmkit/scripts/validate-prizm-docs.py"',
617
- ' if not prizmkit_validate.is_file():',
618
- ' prizmkit_validate = prizmkit_root / ".prizmkit/dev-pipeline/scripts/validate-prizm-docs.py"',
619
- ' if prizmkit_validate.is_file():',
620
- ' prizmkit_result = subprocess.run([sys.executable, str(prizmkit_validate), "--staged"], cwd=prizmkit_root, check=False)',
657
+ ' prizmkit_pre_commit = prizmkit_root / ".prizmkit/scripts/prizm-pre-commit.py"',
658
+ ' prizmkit_diff = subprocess.run(["git", "diff", "--cached", "--quiet", "--", ".prizmkit/prizm-docs"], cwd=prizmkit_root, check=False)',
659
+ ' if prizmkit_pre_commit.is_file():',
660
+ ' prizmkit_result = subprocess.run([sys.executable, str(prizmkit_pre_commit)], cwd=prizmkit_root, check=False)',
621
661
  ' if prizmkit_result.returncode != 0:',
622
662
  ' raise SystemExit(prizmkit_result.returncode)',
663
+ ' elif prizmkit_diff.returncode != 0:',
664
+ ' print("PrizmKit: staged Prizm documents require the installed validator.", file=sys.stderr)',
665
+ ' raise SystemExit(1)',
623
666
  PRIZMKIT_HOOK_MARKER_END,
624
667
  '',
625
668
  ].join('\n');
@@ -630,7 +673,10 @@ function buildAppendedPrizmHook(templateContent, userHookContent) {
630
673
 
631
674
  function insertPrizmHookIntoUserHook(userHookContent, prizmHookBlock) {
632
675
  const content = userHookContent.trimEnd();
633
- const terminalSuccessExitPattern = /(^|\n)([ \t]*exit[ \t]+0[ \t]*(?:#.*)?)[ \t]*$/;
676
+ const isPython = /^#!.*\bpython[0-9.]*\b/i.test(content);
677
+ const terminalSuccessExitPattern = isPython
678
+ ? /(^|\n)([ \t]*(?:sys\.exit\(0\)|raise[ \t]+SystemExit\(0\)|exit\(0\))[ \t]*(?:#.*)?)[ \t]*$/
679
+ : /(^|\n)([ \t]*exit[ \t]+0[ \t]*(?:#.*)?)[ \t]*$/;
634
680
  const match = content.match(terminalSuccessExitPattern);
635
681
 
636
682
  if (!match) {
@@ -760,9 +806,11 @@ export async function installPrizmkitScripts(projectRoot, dryRun, runtime = 'pyt
760
806
 
761
807
  const scripts = [
762
808
  'validate-prizm-docs.py',
809
+ 'prizm-pre-commit.py',
763
810
  'diff-prizm-docs.py',
764
811
  'commit-intent-status.py',
765
812
  'post-command-prizm-drift.py',
813
+ 'run-python-hook.cjs',
766
814
  ];
767
815
  const legacyManagedScripts = [
768
816
  'validate-prizm-docs.sh',
@@ -1277,8 +1325,13 @@ export async function installGitignore(projectRoot, options = {}, dryRun) {
1277
1325
  privateMemoryFiles: options.privateMemoryFiles || [],
1278
1326
  });
1279
1327
 
1328
+ if (!templateContent) {
1329
+ if (dryRun) console.log(chalk.gray(' [dry-run] .gitignore (no managed private-memory entries)'));
1330
+ return;
1331
+ }
1332
+
1280
1333
  if (dryRun) {
1281
- console.log(chalk.gray(` [dry-run] .gitignore`));
1334
+ console.log(chalk.gray(` [dry-run] .gitignore (private host memory only)`));
1282
1335
  return;
1283
1336
  }
1284
1337
 
@@ -1291,14 +1344,15 @@ export async function installGitignore(projectRoot, options = {}, dryRun) {
1291
1344
 
1292
1345
  if (missing.length > 0) {
1293
1346
  const separator = existing.endsWith('\n') ? '' : '\n';
1294
- const header = existingLines.has('# PrizmKit') ? '' : '# PrizmKit\n';
1347
+ const managedHeader = '# PrizmKit private host memory';
1348
+ const header = existingLines.has(managedHeader) ? '' : `${managedHeader}\n`;
1295
1349
  await fs.appendFile(targetPath, separator + '\n' + header + missing.join('\n') + '\n');
1296
1350
  console.log(chalk.green(` ✓ .gitignore (added ${missing.length} entries)`));
1297
1351
  } else {
1298
1352
  console.log(chalk.green(' ✓ .gitignore (up-to-date)'));
1299
1353
  }
1300
1354
  } else {
1301
- await fs.writeFile(targetPath, templateContent);
1355
+ await fs.writeFile(targetPath, `${templateContent}\n`);
1302
1356
  console.log(chalk.green(' ✓ .gitignore'));
1303
1357
  }
1304
1358
  }
@@ -1,89 +0,0 @@
1
- # Deployment Document Template
2
-
3
- Render `.prizmkit/deploy/deploy.md` with the exact top-level headings below. Replace placeholders with established values, use `not configured` or `not applicable` when truthful, and never write raw secret values. Update sections in place; do not append duplicate headings or turn this document into deployment history. Immutable operation events belong in `deploy-history/`.
4
-
5
- ```markdown
6
- # Deployment
7
-
8
- ## Target
9
-
10
- - Project: <project name>
11
- - Project type: <detected project type>
12
- - Environment: <dev | test | production>
13
- - Target: <SSH host alias | cloud platform | Docker | unsupported target>
14
- - Adapter: <ssh | cloud-guided | docker-guided | documented-fallback>
15
- - Strategy: <push | pull | direct-upload | platform-native | container-native | manual>
16
- - Public URL: <URL | not configured>
17
-
18
- ## Prerequisites
19
-
20
- - <required runtime/tool/account/connectivity prerequisite>
21
-
22
- ## Required Environment Variables
23
-
24
- Record names, purpose, source, and presence only. Never record values.
25
-
26
- | Name | Required | Purpose | Secret Source | Present |
27
- |---|---|---|---|---|
28
- | `<VARIABLE_NAME>` | yes | <purpose> | <platform secret store or ignored local env file> | yes/no |
29
-
30
- ## Build and Release
31
-
32
- - Install command: `<command | not required>`
33
- - Build command: `<command | not required>`
34
- - Start command: `<command>`
35
- - Release layout: <path/layout or platform-native>
36
- - Artifact source: <repository checkout | uploaded archive | container image | platform build>
37
-
38
- ## Runtime
39
-
40
- - Process manager: <manager | platform-native>
41
- - Runtime user: <user | platform-managed>
42
- - Internal port: <port | platform-managed>
43
- - Public routing: <Nginx/proxy/platform route>
44
- - Persistent storage: <mounts/volumes | none>
45
-
46
- ## Health Checks
47
-
48
- | Name | URL or Command | Expected | Last Verified Result |
49
- |---|---|---|---|
50
- | <check> | <path/command> | <status/output> | <pass/fail/not run> |
51
-
52
- ## DNS and TLS
53
-
54
- - Domain: <domain | not configured>
55
- - DNS status: <configured/pending/not applicable>
56
- - TLS issuer: <issuer | platform-managed | not configured>
57
- - Renewal: <automatic/manual/not applicable>
58
-
59
- ## Rollback
60
-
61
- - Strategy: <previous release/platform rollback/image rollback/manual>
62
- - Command or procedure: <safe rollback command/procedure>
63
- - Readiness: <verified/not verified/not applicable>
64
-
65
- ## Operations
66
-
67
- - Status: `<command or platform action>`
68
- - Logs: `<command or platform action>`
69
- - Restart: `<command or platform action>`
70
- - Health: `<command or platform action>`
71
- - Rollback: `<command or platform action>`
72
-
73
- ## Current Validation
74
-
75
- - Configuration: <pass/fail/not run>
76
- - Connectivity: <pass/fail/not run>
77
- - Build: <pass/fail/not run>
78
- - Staged health: <pass/fail/not run>
79
- - Live health: <pass/fail/not run>
80
- - Latest history record: `.prizmkit/deploy/deploy-history/<event-id>.json` or `none`
81
-
82
- ## Manual Actions
83
-
84
- - <remaining user-owned action, or `None`>
85
-
86
- ## Notes
87
-
88
- - <stable deployment constraint, adapter gap, or operational warning>
89
- ```
@@ -1,145 +0,0 @@
1
- # CI/CD Workflow Templates
2
-
3
- Read this file when `deployStrategy` is `ci-cd-push` or `ci-cd-pull`.
4
-
5
- ## Shared Configuration
6
-
7
- **Secrets** (add to GitHub repository Settings → Secrets and variables → Actions):
8
- - `SSH_HOST` — server IP/hostname
9
- - `SSH_USER` — runtime user (e.g., `deploy`)
10
- - `SSH_KEY` — SSH private key
11
- - `SSH_PORT` — SSH port (default 22)
12
-
13
- **Shared trigger:**
14
- ```yaml
15
- on:
16
- push:
17
- branches: [<branch>]
18
- paths-ignore:
19
- - '.prizmkit/**'
20
- - 'docs/**'
21
- ```
22
-
23
- ---
24
-
25
- ## Push Mode Workflow (`ci-cd-push`)
26
-
27
- Build happens on GitHub Actions runner. Only built artifacts are transferred to the server.
28
-
29
- ```yaml
30
- name: Deploy to Production (Push)
31
-
32
- on:
33
- push:
34
- branches: [<branch>]
35
-
36
- jobs:
37
- deploy:
38
- runs-on: ubuntu-latest
39
- steps:
40
- - uses: actions/checkout@v4
41
-
42
- - uses: actions/setup-node@v4
43
- with:
44
- node-version: 20
45
-
46
- - name: Allocate Release Identity
47
- run: echo "ARCHIVE_ID=$(date -u +%Y%m%dT%H%M%SZ)-$(git rev-parse --short=12 HEAD)" >> "$GITHUB_ENV"
48
-
49
- - name: Install & Build
50
- run: |
51
- npm ci
52
- npm run build
53
-
54
- - name: Package & Transfer
55
- run: |
56
- tar czf deploy-$ARCHIVE_ID.tar.gz \
57
- <build-output-dir>/ node_modules/ package.json package-lock.json
58
- scp -P ${{ secrets.SSH_PORT }} deploy-$ARCHIVE_ID.tar.gz \
59
- ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:/var/www/<project>/releases/
60
-
61
- - name: Deploy on Server
62
- uses: appleboy/ssh-action@v1
63
- env:
64
- ARCHIVE_ID: ${{ env.ARCHIVE_ID }}
65
- with:
66
- host: ${{ secrets.SSH_HOST }}
67
- username: ${{ secrets.SSH_USER }}
68
- key: ${{ secrets.SSH_KEY }}
69
- port: ${{ secrets.SSH_PORT }}
70
- envs: ARCHIVE_ID
71
- script: |
72
- set -eu
73
- cd /var/www/<project>
74
- RELEASE_ID="$ARCHIVE_ID"
75
- SUFFIX=1
76
- while [ -e "releases/$RELEASE_ID" ]; do
77
- [ "$SUFFIX" -le 99 ] || { echo "release identity suffixes exhausted" >&2; exit 1; }
78
- RELEASE_ID="$ARCHIVE_ID-$(printf '%02d' "$SUFFIX")"
79
- SUFFIX=$((SUFFIX + 1))
80
- done
81
- mkdir "releases/$RELEASE_ID"
82
- tar xzf "releases/deploy-$ARCHIVE_ID.tar.gz" -C "releases/$RELEASE_ID"
83
- rm "releases/deploy-$ARCHIVE_ID.tar.gz"
84
- # PM2 start, health check, Nginx switch, history, and metadata use RELEASE_ID
85
- ```
86
-
87
- **Key difference from Pull:** the runner checks out code, installs, builds, and only transmits the result. The server doesn't need Git or build tools — just Node.js runtime and PM2.
88
-
89
- ---
90
-
91
- ## Pull Mode Workflow (`ci-cd-pull`)
92
-
93
- GitHub Actions runner only triggers the server. The server does all the work.
94
-
95
- ```yaml
96
- name: Deploy to Production (Pull)
97
-
98
- on:
99
- push:
100
- branches: [<branch>]
101
-
102
- jobs:
103
- deploy:
104
- runs-on: ubuntu-latest
105
- steps:
106
- - uses: actions/checkout@v4
107
-
108
- - name: Allocate Release Identity
109
- run: |
110
- echo "RELEASE_BASE=$(date -u +%Y%m%dT%H%M%SZ)-$(git rev-parse --short=12 HEAD)" >> "$GITHUB_ENV"
111
- echo "DEPLOY_SHA=$(git rev-parse HEAD)" >> "$GITHUB_ENV"
112
-
113
- - name: Trigger Server Deploy
114
- uses: appleboy/ssh-action@v1
115
- env:
116
- RELEASE_BASE: ${{ env.RELEASE_BASE }}
117
- DEPLOY_SHA: ${{ env.DEPLOY_SHA }}
118
- with:
119
- host: ${{ secrets.SSH_HOST }}
120
- username: ${{ secrets.SSH_USER }}
121
- key: ${{ secrets.SSH_KEY }}
122
- port: ${{ secrets.SSH_PORT }}
123
- envs: RELEASE_BASE,DEPLOY_SHA
124
- script: |
125
- set -eu
126
- cd /var/www/<project>
127
- RELEASE_ID="$RELEASE_BASE"
128
- SUFFIX=1
129
- while [ -e "releases/$RELEASE_ID" ]; do
130
- [ "$SUFFIX" -le 99 ] || { echo "release identity suffixes exhausted" >&2; exit 1; }
131
- RELEASE_ID="$RELEASE_BASE-$(printf '%02d' "$SUFFIX")"
132
- SUFFIX=$((SUFFIX + 1))
133
- done
134
- git clone --no-checkout --branch <branch> <repoUrl> "releases/$RELEASE_ID"
135
- git -C "releases/$RELEASE_ID" checkout --detach "$DEPLOY_SHA"
136
- cd "releases/$RELEASE_ID"
137
- npm ci
138
- cp ../../shared/.env.production .
139
- npm run build
140
- # PM2 start, health check, Nginx switch, history, and metadata use RELEASE_ID
141
- ```
142
-
143
- **Key difference from Push:** the workflow is simpler (one step: SSH + run script), but the server needs Git, full build toolchain, and must be able to reach the repo.
144
-
145
- After generating the workflow, verify: the first `git push` to the configured branch will trigger the first deployment. Monitor the GitHub Actions run and report results.