chati-dev 4.5.7 → 4.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/framework/agents/build/dev.md +1 -1
  2. package/framework/agents/deploy/devops.md +1 -1
  3. package/framework/agents/discover/brief.md +1 -1
  4. package/framework/agents/discover/brownfield-wu.md +1 -1
  5. package/framework/agents/discover/greenfield-wu.md +1 -1
  6. package/framework/agents/plan/architect-data-engineer.md +1 -1
  7. package/framework/agents/plan/architect-system.md +1 -1
  8. package/framework/agents/plan/architect.md +1 -1
  9. package/framework/agents/plan/detail.md +1 -1
  10. package/framework/agents/plan/phases.md +1 -1
  11. package/framework/agents/plan/tasks.md +1 -1
  12. package/framework/agents/plan/ux-brand-architect.md +1 -1
  13. package/framework/agents/plan/ux-component-engineer.md +1 -1
  14. package/framework/agents/plan/ux-researcher.md +1 -1
  15. package/framework/agents/plan/ux.md +1 -1
  16. package/framework/agents/quality/qa-implementation.md +1 -1
  17. package/framework/agents/quality/qa-planning.md +1 -1
  18. package/framework/agents/quality/qa-visual.md +1 -1
  19. package/framework/agents/shared/visualizer.md +1 -1
  20. package/framework/config.yaml +2 -2
  21. package/framework/context/root.md +1 -1
  22. package/framework/data/entity-registry.yaml +1 -1
  23. package/framework/hooks/license-guard.js +19 -0
  24. package/framework/manifest.json +53 -48
  25. package/framework/manifest.sig +1 -1
  26. package/framework/orchestrator/chati-router.js +177 -71
  27. package/framework/orchestrator/chati.md +1 -1
  28. package/framework/package.json +3 -0
  29. package/package.json +1 -1
  30. package/src/config/claude-settings-generator.js +6 -6
  31. package/src/installer/core.js +51 -25
  32. package/src/installer/path-replacement.js +13 -0
  33. package/src/installer/provider-overlay.js +3 -1
  34. package/src/installer-v2/index.js +55 -0
@@ -13,7 +13,7 @@ You are the **Dev Agent**, responsible for implementing code based on the approv
13
13
  - **Duration**: Varies per task
14
14
  - **Ratio**: 20% Human / 80% AI (interactive) or 5% Human / 95% AI (autonomous)
15
15
  - **Absorbs**: Dev personas, self-validation patterns, Design System token enforcement, Ralph Wiggum autonomous mode, blocker taxonomy
16
- - **Model**: opus | no downgrade (code generation requires highest quality)
16
+ - **Execution binding**: resolved at runtime by installation-v2 task routing; never self-select or downgrade
17
17
  - **Provider**: claude (default) | gemini (when large codebase tasks)
18
18
 
19
19
  ## Required MCPs
@@ -12,7 +12,7 @@ You are the **DevOps Agent**, responsible for shipping the project: Git operatio
12
12
  - **Question Answered**: SHIP it
13
13
  - **Duration**: 15-30 min
14
14
  - **Ratio**: 30% Human / 70% AI
15
- - **Model**: sonnet | upgrade: opus if multi-environment or infrastructure-as-code
15
+ - **Execution binding**: resolved at runtime by installation-v2 task routing; never self-select or downgrade
16
16
  - **Provider**: claude (default)
17
17
  - **Absorbs**: Docs-gen (auto documentation generation)
18
18
 
@@ -12,7 +12,7 @@ You are the **Brief Agent**, responsible for extracting and structuring the core
12
12
  - **Question Answered**: WHAT is the problem?
13
13
  - **Duration**: 30-60 min
14
14
  - **Ratio**: 90% Human / 10% AI
15
- - **Model**: sonnet | upgrade: opus if enterprise or 10+ integrations
15
+ - **Execution binding**: resolved at runtime by installation-v2 task routing; never self-select or downgrade
16
16
  - **Provider**: claude (default)
17
17
 
18
18
  ## Required MCPs
@@ -12,7 +12,7 @@ You are the **Brownfield WU Agent**, responsible for comprehensive discovery of
12
12
  - **Question Answered**: HOW does it work today? (both code and operations)
13
13
  - **Duration**: 1-4h depending on codebase size
14
14
  - **Ratio**: 40% Human / 60% AI
15
- - **Model**: opus | no downgrade (codebase analysis requires deep reasoning)
15
+ - **Execution binding**: resolved at runtime by installation-v2 task routing; never self-select or downgrade
16
16
  - **Provider**: claude (default) | gemini (when codebase > 100K LOC)
17
17
 
18
18
  ## Required MCPs
@@ -12,7 +12,7 @@ You are the **Greenfield WU Agent**, responsible for understanding HOW the user
12
12
  - **Question Answered**: HOW does it work today?
13
13
  - **Duration**: 15-30 min
14
14
  - **Ratio**: 70% Human / 30% AI
15
- - **Model**: haiku | upgrade: sonnet if multi-stack or enterprise
15
+ - **Execution binding**: resolved at runtime by installation-v2 task routing; never self-select or downgrade
16
16
  - **Provider**: claude (default)
17
17
 
18
18
  ## Required MCPs
@@ -9,7 +9,7 @@ You are the **Data Engineer**, a specialist sub-agent of the Architect Manager.
9
9
  - **Role**: Database Architecture & Data Operations Specialist
10
10
  - **Parent**: Architect Manager
11
11
  - **Step**: 4 (Data Architecture - expanded from original Architect)
12
- - **Model**: opus | no downgrade (schema design requires deep reasoning about relationships and constraints)
12
+ - **Execution binding**: resolved at runtime by installation-v2 task routing; never self-select or downgrade
13
13
 
14
14
  ---
15
15
 
@@ -9,7 +9,7 @@ You are the **System Architect**, a specialist sub-agent of the Architect Manage
9
9
  - **Role**: System Architecture & API Design Specialist
10
10
  - **Parent**: Architect Manager
11
11
  - **Steps**: 1-3 (Tech Stack, System Design, API + Auth + Deploy + Security)
12
- - **Model**: opus | no downgrade (architecture requires deep reasoning)
12
+ - **Execution binding**: resolved at runtime by installation-v2 task routing; never self-select or downgrade
13
13
 
14
14
  ---
15
15
 
@@ -12,7 +12,7 @@ You are the **Architect Manager**, the coordinator for the technical design pipe
12
12
  - **Question Answered**: HOW will we build it?
13
13
  - **Duration**: 45-90 min (coordinates 2 sub-agents sequentially)
14
14
  - **Ratio**: 50% Human / 50% AI
15
- - **Model**: opus | no downgrade
15
+ - **Execution binding**: resolved at runtime by installation-v2 task routing; never self-select or downgrade
16
16
  - **Provider**: claude (default)
17
17
 
18
18
  ## Required MCPs
@@ -13,7 +13,7 @@ You are the **Detail Agent**, responsible for transforming the Brief into a form
13
13
  - **Duration**: 45-90 min
14
14
  - **Ratio**: 70% Human / 30% AI
15
15
  - **Absorbs**: PM (PRD creation, product strategy), Analyst (market research, competitive analysis)
16
- - **Model**: opus | no downgrade (PRD traceability requires deep reasoning)
16
+ - **Execution binding**: resolved at runtime by installation-v2 task routing; never self-select or downgrade
17
17
  - **Provider**: claude (default)
18
18
 
19
19
  ## Required MCPs
@@ -13,7 +13,7 @@ You are the **Phases Agent**, responsible for breaking the PRD into development
13
13
  - **Duration**: 20-40 min
14
14
  - **Ratio**: 60% Human / 40% AI
15
15
  - **Absorbs**: SM (phase planning, prioritization, wave sequencing)
16
- - **Model**: sonnet | upgrade: opus if 20+ requirements or complex dependencies
16
+ - **Execution binding**: resolved at runtime by installation-v2 task routing; never self-select or downgrade
17
17
  - **Provider**: claude (default)
18
18
 
19
19
  ## Required MCPs
@@ -13,7 +13,7 @@ You are the **Tasks Agent**, responsible for breaking phases into atomic, execut
13
13
  - **Duration**: 30-60 min
14
14
  - **Ratio**: 50% Human / 50% AI
15
15
  - **Absorbs**: SM (structured task creation, Given-When-Then criteria)
16
- - **Model**: sonnet | upgrade: opus if 50+ tasks or complex acceptance criteria
16
+ - **Execution binding**: resolved at runtime by installation-v2 task routing; never self-select or downgrade
17
17
  - **Provider**: claude (default)
18
18
 
19
19
  ## Required MCPs
@@ -9,7 +9,7 @@ You are the **Brand & Design System Architect**, a specialist sub-agent of the U
9
9
  - **Role**: Brand Identity & Design System Specialist
10
10
  - **Parent**: UX Manager
11
11
  - **Phases**: 0 (Brand Identity) + 4 (Design System Definition)
12
- - **Model**: sonnet | upgrade: opus if design system creation from scratch
12
+ - **Execution binding**: resolved at runtime by installation-v2 task routing; never self-select or downgrade
13
13
 
14
14
  ---
15
15
 
@@ -9,7 +9,7 @@ You are the **Component Engineer**, a specialist sub-agent of the UX Manager. Yo
9
9
  - **Role**: Component Architecture & Accessibility Specialist
10
10
  - **Parent**: UX Manager
11
11
  - **Phases**: 3 (Interaction Patterns + Accessibility + Motion + Component Discovery)
12
- - **Model**: sonnet | upgrade: opus if complex component architecture
12
+ - **Execution binding**: resolved at runtime by installation-v2 task routing; never self-select or downgrade
13
13
 
14
14
  ---
15
15
 
@@ -9,7 +9,7 @@ You are the **UX Researcher**, a specialist sub-agent of the UX Manager. You own
9
9
  - **Role**: User Experience Research & Information Architecture Specialist
10
10
  - **Parent**: UX Manager
11
11
  - **Phases**: 1 (User Flow Mapping) + 2 (Information Architecture)
12
- - **Model**: sonnet
12
+ - **Execution binding**: resolved at runtime by installation-v2 task routing; never self-select or downgrade
13
13
 
14
14
  ---
15
15
 
@@ -12,7 +12,7 @@ You are the **UX Manager**, the coordinator for the design experience pipeline.
12
12
  - **Question Answered**: HOW will it look/feel?
13
13
  - **Duration**: 60-120 min (coordinates 3 sub-agents sequentially)
14
14
  - **Ratio**: 30% Human / 70% AI (user validates direction, sub-agents execute)
15
- - **Model**: sonnet | upgrade: opus if design system creation from scratch
15
+ - **Execution binding**: resolved at runtime by installation-v2 task routing; never self-select or downgrade
16
16
  - **Provider**: claude (default)
17
17
 
18
18
  ## Required MCPs
@@ -12,7 +12,7 @@ You are the **QA-Implementation Agent**, the quality gate between BUILD and DEPL
12
12
  - **Question Answered**: DOES it work correctly?
13
13
  - **Duration**: 15-45 min (mostly automated)
14
14
  - **Ratio**: 90% AI / 10% Human
15
- - **Model**: opus | no downgrade (code review and SAST require deep reasoning)
15
+ - **Execution binding**: resolved at runtime by installation-v2 task routing; never self-select or downgrade
16
16
  - **Provider**: claude (default)
17
17
  - **Absorbs**: Tests + SAST + CodeRabbit code review
18
18
 
@@ -12,7 +12,7 @@ You are the **QA-Planning Agent**, the quality gate between PLANNING (planning)
12
12
  - **Question Answered**: IS everything traceable and rigorous?
13
13
  - **Duration**: 15-30 min (automated validation)
14
14
  - **Ratio**: 95% AI / 5% Human
15
- - **Model**: opus | no downgrade (cross-artifact validation requires deep reasoning)
15
+ - **Execution binding**: resolved at runtime by installation-v2 task routing; never self-select or downgrade
16
16
  - **Provider**: claude (default)
17
17
  - **Absorbs**: Manager (cross-artifact validation), PO (quality gate validation, coherence checks)
18
18
 
@@ -19,7 +19,7 @@ You are the **QA-Visual Agent**, a specialized quality gate focused exclusively
19
19
  - **Question Answered**: DOES it look right?
20
20
  - **Duration**: 5-15 min (mostly automated via visual-qa.js)
21
21
  - **Ratio**: 80% AI / 20% Human
22
- - **Model**: opus | no downgrade (screenshot analysis requires multimodal reasoning)
22
+ - **Execution binding**: resolved at runtime by installation-v2 task routing; never self-select or downgrade
23
23
  - **Provider**: claude (default)
24
24
  - **Absorbs**: Phase 5d + Phase 5e from QA-Implementation (visual review and motion quality)
25
25
 
@@ -13,7 +13,7 @@ You are the **Visualizer**, a specialized sub-agent that reads sequential screen
13
13
  ## Identity
14
14
 
15
15
  - **Role**: Visual Frame Analyst (sub-agent, never runs standalone)
16
- - **Model**: sonnet (cost-efficient multimodal image reading, not deep reasoning)
16
+ - **Execution binding**: resolved at runtime by installation-v2 task routing; never self-select or downgrade
17
17
  - **Spawned by**: Brief (reference analysis) or QA-Visual (build validation)
18
18
  - **Output**: Text-only analysis returned to the parent agent
19
19
 
@@ -1,8 +1,8 @@
1
1
  # chati.dev Configuration
2
- version: "4.5.7"
2
+ version: "4.5.8"
3
3
  installed_at: "2026-02-07T10:00:00Z"
4
4
  updated_at: "2026-08-27T00:00:00Z"
5
- installer_version: "4.5.7"
5
+ installer_version: "4.5.8"
6
6
  project_type: greenfield
7
7
  language: en
8
8
  ides: []
@@ -1,7 +1,7 @@
1
1
  # Chati.dev System Context
2
2
 
3
3
  ## Framework
4
- - **Version**: 4.5.7
4
+ - **Version**: 4.5.8
5
5
  - **Agents**: Specialized discovery and planning agents feeding provider-neutral RAIL execution and a sealed release lane
6
6
  - **Constitution**: 25 Articles + Preamble
7
7
  - **Quality**: 5 pipeline gates + 3-tier verdicts + Fault Vector Protocol (Article XXII)
@@ -3,7 +3,7 @@
3
3
  # and by the Health Check for system integrity validation.
4
4
 
5
5
  metadata:
6
- version: "4.5.7"
6
+ version: "4.5.8"
7
7
  last_updated: "2026-08-27T00:00:00Z"
8
8
  entity_count: 109
9
9
  checksum_algorithm: sha256
@@ -29,6 +29,19 @@ const CACHE_TTL_MS = 4 * 60 * 60 * 1000; // 4 hours
29
29
  const GLOBAL_DIR = join(homedir(), '.chati-dev');
30
30
  const LICENSE_PATH = join(GLOBAL_DIR, 'license.yaml');
31
31
 
32
+ async function hasVerifiedOpenInstallation(projectDir = process.cwd()) {
33
+ const artifactPath = join(projectDir, '.chati', 'v2', 'installation.json');
34
+ const doctorPath = join(projectDir, '.chati.dev', '_cli', 'installer-v2', 'index.js');
35
+ if (!existsSync(artifactPath) || !existsSync(doctorPath)) return false;
36
+ try {
37
+ const artifact = JSON.parse(readFileSync(artifactPath, 'utf-8'));
38
+ const { doctorV2 } = await import(doctorPath);
39
+ return doctorV2(artifact).passed && artifact.installation?.profile === 'open';
40
+ } catch {
41
+ return false;
42
+ }
43
+ }
44
+
32
45
  /**
33
46
  * Pure license validation function — provider-agnostic.
34
47
  * Returns a verdict object that hook wrappers can translate to their
@@ -38,6 +51,12 @@ const LICENSE_PATH = join(GLOBAL_DIR, 'license.yaml');
38
51
  * @returns {Promise<{valid: boolean, reason?: string, status?: string}>}
39
52
  */
40
53
  export async function checkLicense() {
54
+ // The public Open profile does not require a commercial license. Trust the
55
+ // profile only after the canonical v2 doctor validates the signed artifact.
56
+ if (await hasVerifiedOpenInstallation()) {
57
+ return { valid: true, status: 'NOT_REQUIRED', profile: 'open' };
58
+ }
59
+
41
60
  // Read global license file — REQUIRED for operation
42
61
  if (!existsSync(LICENSE_PATH)) {
43
62
  return { valid: false, reason: 'No license found. Run: npx chati-dev activate --key=YOUR-KEY\nGet a license at https://chati.dev/pricing', status: 'MISSING' };
@@ -1,103 +1,103 @@
1
1
  {
2
- "createdAt": "2026-08-27T10:57:24.520Z",
2
+ "createdAt": "2026-08-27T12:02:42.275Z",
3
3
  "files": {
4
4
  "agents/build/dev.md": {
5
- "hash": "5c8da2eee3357bbe84a6c468c76fe60a0c77357575503c3797aa94128282c565",
6
- "size": 64929,
5
+ "hash": "13c2b3855c1f815154a2f4020b99fd8ee71e701b41783f5f26696222720a1466",
6
+ "size": 64962,
7
7
  "type": "md"
8
8
  },
9
9
  "agents/deploy/devops.md": {
10
- "hash": "556488072417f4ed5cb7a358ea40f1946283b93041236fcedb770fb404df8dd3",
11
- "size": 17999,
10
+ "hash": "f751c5d95d690627c45d885d0a484c2019aacda4111cb9785b70ea1813e67f40",
11
+ "size": 18025,
12
12
  "type": "md"
13
13
  },
14
14
  "agents/discover/brief.md": {
15
- "hash": "d429a0d1bfb94f5d12c264bef90f0c5752c2500e57bdbdd6020ff84412fc9ff3",
16
- "size": 26154,
15
+ "hash": "f5980b1c622103879b8fec400ab4461ba9bbdc0b60f498ddcdcdc4c8504ee919",
16
+ "size": 26193,
17
17
  "type": "md"
18
18
  },
19
19
  "agents/discover/brownfield-wu.md": {
20
- "hash": "9b0f4eca1194d2d8a7e92c34cfa9357c774750299a79d0afeda06d1fe8d57a3e",
21
- "size": 18088,
20
+ "hash": "b89e20408bdb0c672d6799507b0f9679c76df7f87f183f27b325070174ac490f",
21
+ "size": 18120,
22
22
  "type": "md"
23
23
  },
24
24
  "agents/discover/greenfield-wu.md": {
25
- "hash": "abbced7c06b617584241fb4e1926315a2f6dcb651a36f7598315dbc7b67a6c83",
26
- "size": 17171,
25
+ "hash": "046d29a39c85faaae7f67e820f8755d08b84d18ce880041208b6d9432e00eac8",
26
+ "size": 17214,
27
27
  "type": "md"
28
28
  },
29
29
  "agents/plan/architect-data-engineer.md": {
30
- "hash": "86df68c465d69443d01f4e01b1a897516a3216fc1ef0c79be1f8ecf867a0fa89",
30
+ "hash": "092e76bf23eda8634863f05e1557a29044ec7afbde4cce3b103db26363a2cbc5",
31
31
  "size": 10435,
32
32
  "type": "md"
33
33
  },
34
34
  "agents/plan/architect-system.md": {
35
- "hash": "bc5a92d013cc56448d4ed59366622217fdf90c90d083a5ae698d28cdbac06a8e",
36
- "size": 8753,
35
+ "hash": "e05732c0a944be2c4d52cb3cd394a521c10c7a5d04569abb9f5f9111a1db1ceb",
36
+ "size": 8790,
37
37
  "type": "md"
38
38
  },
39
39
  "agents/plan/architect.md": {
40
- "hash": "94c5eeb672d172d39e4ca1e189cfbb772fae262631c17cd4c0d11227be4d0739",
41
- "size": 13213,
40
+ "hash": "d3d39eb5ac360b4945187dd6ad6b00bb096c09de2cf99232e881b91a8ae1c04a",
41
+ "size": 13289,
42
42
  "type": "md"
43
43
  },
44
44
  "agents/plan/detail.md": {
45
- "hash": "1f5ae252b10cbc4142ff3be835344567db139e4e03dc5ffedd9653ff6e4115cb",
46
- "size": 19258,
45
+ "hash": "310903bd1e7edc79ccd30ee83ccdfdea1b18f745629f6e2892a001f777771f6a",
46
+ "size": 19291,
47
47
  "type": "md"
48
48
  },
49
49
  "agents/plan/phases.md": {
50
- "hash": "123bc5a1e12ad67777a1fac7d1953e3f764f49638b1cbef38dce991316630e15",
51
- "size": 15834,
50
+ "hash": "04f53642c0624da30cc74ec26298731480d016e959ed3538f40054ea5a95d8af",
51
+ "size": 15863,
52
52
  "type": "md"
53
53
  },
54
54
  "agents/plan/tasks.md": {
55
- "hash": "16c8c649a2172ca741b69fb5d2495f913afae981d57af97cc0503eba05af915b",
56
- "size": 28228,
55
+ "hash": "d6440ac7e7ecc9a24d89f39b1786e3cb218565a16b12ecba888e8899abe57b48",
56
+ "size": 28257,
57
57
  "type": "md"
58
58
  },
59
59
  "agents/plan/ux-brand-architect.md": {
60
- "hash": "9f9b586caf065402f493a1dcbb69ac70c575f595dc07021c7f46cc7e51a5490b",
61
- "size": 28368,
60
+ "hash": "7bb987949fb404115735c82085c906db7a6b7b1baa4e6672f442114fe6c554b7",
61
+ "size": 28402,
62
62
  "type": "md"
63
63
  },
64
64
  "agents/plan/ux-component-engineer.md": {
65
- "hash": "7bc21a8b57a929420df4009895a841381c759a79174c18beeeb4c24a2a25dc7d",
66
- "size": 19663,
65
+ "hash": "65c72804f9ee40f814493b0e4a3db6d9d12f8ce34f8a4425d46a4bd23d522aa2",
66
+ "size": 19702,
67
67
  "type": "md"
68
68
  },
69
69
  "agents/plan/ux-researcher.md": {
70
- "hash": "1f489f01273296539c23965f5adfd87a994fb8e52d19308b73474486b3051095",
71
- "size": 6484,
70
+ "hash": "230555bfa77ced7196cf082ccc3f6de2b531b81063082c8d5af61d81ef37c608",
71
+ "size": 6573,
72
72
  "type": "md"
73
73
  },
74
74
  "agents/plan/ux.md": {
75
- "hash": "6cf80ed6d8f4694ed027402d648b1d7367a5b3f3e7c4bce54c86d1d109ad671f",
76
- "size": 15218,
75
+ "hash": "1b4303659b12a4cc687a7b14da52c51d4f72ab16b892cd2c074a818c96cacbe1",
76
+ "size": 15252,
77
77
  "type": "md"
78
78
  },
79
79
  "agents/quality/qa-implementation.md": {
80
- "hash": "e3ea4d10856db04f91ea49714dc1f93bc2acf504edc9d93902d2a030c778d790",
81
- "size": 40336,
80
+ "hash": "33d48d86473bd23b137baefb62ccfb5ad3f5a3e515779e2a19acc54ebe1de62b",
81
+ "size": 40366,
82
82
  "type": "md"
83
83
  },
84
84
  "agents/quality/qa-planning.md": {
85
- "hash": "70135407c6bf8196a37cf52413c6462c0703ebc1fa59e511491c6360caf1dfd7",
86
- "size": 25774,
85
+ "hash": "82cf821a4a04b784788f3b53281e7aa49abd4e72a88d9fcd42f2cc5c5d9a0f6d",
86
+ "size": 25798,
87
87
  "type": "md"
88
88
  },
89
89
  "agents/quality/qa-visual.md": {
90
- "hash": "1fd18ad99315b6400a404acb307b0d33b18743429f8facb915f6127f57cdf57d",
91
- "size": 21496,
90
+ "hash": "c4b7ca7ec0378c6a51092adfcaf58f811796711f713b65ac12408529374a72e9",
91
+ "size": 21520,
92
92
  "type": "md"
93
93
  },
94
94
  "agents/shared/visualizer.md": {
95
- "hash": "6be88248ba3e5b02b900364e4729f55f4bb24031508f25ad7cbb664689ddd328",
96
- "size": 4352,
95
+ "hash": "4110dcb1c2e440f31d9820499c7e946b58ac7fe2b9e7d29f68133c3c8f2021e9",
96
+ "size": 4375,
97
97
  "type": "md"
98
98
  },
99
99
  "config.yaml": {
100
- "hash": "394515115a919ac4b838cd58b3cf8f7cb34b535909b9a35f2d472cb51cc107ad",
100
+ "hash": "00aa2be05f8cd997b3aae234f56969e1b3309af68c35ada1c439e66cefb37ae5",
101
101
  "size": 2669,
102
102
  "type": "yaml"
103
103
  },
@@ -122,12 +122,12 @@
122
122
  "type": "md"
123
123
  },
124
124
  "context/root.md": {
125
- "hash": "1058040823bbd394775a017f6b714b5f53ff6030f08113c191d1a477f8635e95",
125
+ "hash": "f04a8dc22486c1f0cc29ea2ec5ad30d4a1c343823f1dfc5674946acb854106bb",
126
126
  "size": 1217,
127
127
  "type": "md"
128
128
  },
129
129
  "data/entity-registry.yaml": {
130
- "hash": "8049c68d09ed547d64d1120f10a3f064f1c4f8313eacf8c2d267808e83a07265",
130
+ "hash": "d11e07cb0ab2790938af077da954580f9bf0d7230bf4e79b59e6c45a00e2dcb7",
131
131
  "size": 34605,
132
132
  "type": "yaml"
133
133
  },
@@ -307,8 +307,8 @@
307
307
  "type": "js"
308
308
  },
309
309
  "hooks/license-guard.js": {
310
- "hash": "863b7ea89d1235e6f48dd777e9fc341cd5581308e9026c83783199c93f4049a1",
311
- "size": 10920,
310
+ "hash": "40b0d044e2dac77fca826825e52119b1f2293a3b63225d1aecc9116ed7dc695d",
311
+ "size": 11759,
312
312
  "type": "js"
313
313
  },
314
314
  "hooks/mode-governance.js": {
@@ -462,8 +462,8 @@
462
462
  "type": "yaml"
463
463
  },
464
464
  "orchestrator/chati-router.js": {
465
- "hash": "f686b16519162aee6fd524e970ba7f35e63b617d47b563a74abad01245aeff06",
466
- "size": 16390,
465
+ "hash": "f5c3e7b8285d32cbb1e1f9e0bfda23f8996a4bb3e1b3966038b41d06aacc50d8",
466
+ "size": 20793,
467
467
  "type": "js"
468
468
  },
469
469
  "orchestrator/chati-update.md": {
@@ -472,10 +472,15 @@
472
472
  "type": "md"
473
473
  },
474
474
  "orchestrator/chati.md": {
475
- "hash": "6ce86145704af5e933b7372ccd331e70679066522c324031f4e8701b7f950cc5",
476
- "size": 45331,
475
+ "hash": "8d55e3e354e7e7cf4d28ca3aba5372df0580ca3b191e2e14696363867eba567a",
476
+ "size": 45355,
477
477
  "type": "md"
478
478
  },
479
+ "package.json": {
480
+ "hash": "3ca9d4afd21425087cf31893b8f9f63c81b0b8408db5e343ca76e5f8aa26ab9a",
481
+ "size": 23,
482
+ "type": "json"
483
+ },
479
484
  "patterns/elicitation-library.yaml": {
480
485
  "hash": "223780498a36981fa030ae54f2e73bbc68140073b7c30a286465cffe45886f17",
481
486
  "size": 25578,
@@ -1347,5 +1352,5 @@
1347
1352
  "type": "yaml"
1348
1353
  }
1349
1354
  },
1350
- "version": "4.5.7"
1355
+ "version": "4.5.8"
1351
1356
  }
@@ -1 +1 @@
1
- s4/05J4PlrCMfsXB3OMeVX+lE+7A2MMEycfr10Z+2uwgFh5DlWnSGxkMmCS4dZ3hVAWgSWDk7waAR8kA1JWNAw==
1
+ stchaq/GjnloDsRgwm8vCQpA4vFKsofjuyMg5b+4aU6pEblso6UgEAtPPlRkVVkxcmn8ctX9YxQmqe+MmaPsAA==
@@ -21,6 +21,125 @@ import { join } from 'path';
21
21
  const PROJECT_DIR = process.cwd();
22
22
  const ROUTER_ARGS = process.argv.slice(2);
23
23
 
24
+ async function readEnabledHarnesses(projectDir) {
25
+ const installationPath = join(projectDir, '.chati', 'v2', 'installation.json');
26
+ if (existsSync(installationPath)) {
27
+ try {
28
+ const artifact = JSON.parse(readFileSync(installationPath, 'utf-8'));
29
+ const fwDir = existsSync(join(projectDir, '.chati.dev')) ? '.chati.dev' : 'chati.dev';
30
+ const doctorModulePath = join(projectDir, fwDir, '_cli', 'installer-v2', 'index.js');
31
+ if (!existsSync(doctorModulePath)) return { harnesses: [], error: 'v2 doctor missing' };
32
+ const { doctorV2 } = await import(doctorModulePath);
33
+ if (!doctorV2(artifact).passed) return { harnesses: [], error: 'v2 artifact invalid' };
34
+ const harnesses = artifact.installation?.enabled_providers
35
+ ?.map((binding) => binding.harness_id)
36
+ .filter(Boolean);
37
+ if (harnesses?.length) return { harnesses: [...new Set(harnesses)], error: null };
38
+ } catch { return { harnesses: [], error: 'v2 artifact invalid' }; }
39
+ }
40
+
41
+ const detected = [];
42
+ if (existsSync(join(projectDir, '.claude', 'commands', 'chati.md'))) detected.push('claude');
43
+ if (existsSync(join(projectDir, '.agents', 'skills', 'chati', 'SKILL.md'))) detected.push('codex');
44
+ if (existsSync(join(projectDir, '.grok', 'commands', 'chati.md'))) detected.push('grok');
45
+ return { harnesses: detected, error: null };
46
+ }
47
+
48
+ function hasContent(path) {
49
+ return existsSync(path) && readFileSync(path, 'utf-8').trim().length > 0;
50
+ }
51
+
52
+ function codexHooksEnabled(config) {
53
+ if (/^\s*features\.hooks\s*=\s*true\s*(?:#.*)?$/m.test(config)) return true;
54
+ const lines = config.split('\n');
55
+ const sectionStart = lines.findIndex((line) => /^\s*\[features\]\s*(?:#.*)?$/.test(line));
56
+ if (sectionStart === -1) return false;
57
+ const sectionEnd = lines.findIndex((line, index) => index > sectionStart && /^\s*\[.*\]\s*(?:#.*)?$/.test(line));
58
+ const end = sectionEnd === -1 ? lines.length : sectionEnd;
59
+ return lines.slice(sectionStart + 1, end).some((line) => /^\s*hooks\s*=\s*true\s*(?:#.*)?$/.test(line));
60
+ }
61
+
62
+ export async function validateProviderIntegrity(projectDir) {
63
+ const missing = [];
64
+ const { harnesses, error } = await readEnabledHarnesses(projectDir);
65
+
66
+ if (error) missing.push(error);
67
+ const supportedHarnesses = new Set(['claude', 'codex', 'grok']);
68
+ for (const harness of harnesses) {
69
+ if (!supportedHarnesses.has(harness)) missing.push(`unsupported harness ${harness}`);
70
+ }
71
+
72
+ if (harnesses.length === 0) missing.push('provider entry point');
73
+
74
+ if (harnesses.includes('claude')) {
75
+ const commandPath = join(projectDir, '.claude', 'commands', 'chati.md');
76
+ const settingsPath = join(projectDir, '.claude', 'settings.json');
77
+ if (!hasContent(commandPath)) missing.push('claude command');
78
+ if (!existsSync(settingsPath)) {
79
+ missing.push('claude settings');
80
+ } else {
81
+ try {
82
+ const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
83
+ const ptu = settings.hooks?.PreToolUse || [];
84
+ const ups = settings.hooks?.UserPromptSubmit || [];
85
+ const ptuCmds = ptu.flatMap((group) => (group.hooks || []).map((hook) => hook.command || ''));
86
+ const upsCmds = ups.flatMap((group) => (group.hooks || []).map((hook) => hook.command || ''));
87
+ missing.push(...['mode-governance', 'constitution-guard', 'read-protection']
88
+ .filter((hook) => !ptuCmds.some((command) => command.includes(hook)))
89
+ .map((hook) => `claude hook ${hook}`));
90
+ if (![...upsCmds, ...ptuCmds].some((command) => command.includes('license-guard'))) {
91
+ missing.push('claude hook license-guard');
92
+ }
93
+ } catch {
94
+ missing.push('claude settings invalid');
95
+ }
96
+ }
97
+ }
98
+
99
+ if (harnesses.includes('codex')) {
100
+ const required = [
101
+ ['codex skill', '.agents/skills/chati/SKILL.md'],
102
+ ['codex constitution guard', '.codex/rules/constitution-guard.rules'],
103
+ ['codex read protection', '.codex/rules/read-protection.rules'],
104
+ ];
105
+ for (const [label, relativePath] of required) {
106
+ if (!hasContent(join(projectDir, relativePath))) missing.push(label);
107
+ }
108
+ const configPath = join(projectDir, '.codex', 'config.toml');
109
+ if (!hasContent(configPath) || !codexHooksEnabled(readFileSync(configPath, 'utf-8'))) {
110
+ missing.push('codex hooks feature');
111
+ }
112
+ const hooksPath = join(projectDir, '.codex', 'hooks.json');
113
+ if (!hasContent(hooksPath)) {
114
+ missing.push('codex license hook');
115
+ } else {
116
+ try {
117
+ const hooks = JSON.parse(readFileSync(hooksPath, 'utf-8'));
118
+ const validHook = (hooks.hooks?.UserPromptSubmit || []).some((group) =>
119
+ group.matcher === '.*'
120
+ && (group.hooks || []).some((hook) =>
121
+ hook.type === 'command'
122
+ && hook.command === 'node .chati.dev/hooks/license-guard.js'
123
+ && hook.async !== true
124
+ )
125
+ );
126
+ if (!validHook) {
127
+ missing.push('codex license hook');
128
+ }
129
+ } catch {
130
+ missing.push('codex hooks invalid');
131
+ }
132
+ }
133
+ }
134
+
135
+ if (harnesses.includes('grok')) {
136
+ if (!hasContent(join(projectDir, '.grok', 'commands', 'chati.md'))) missing.push('grok command');
137
+ if (!hasContent(join(projectDir, '.grok', 'session-lock.md'))) missing.push('grok session lock');
138
+ }
139
+
140
+ return { valid: missing.length === 0, missing };
141
+ }
142
+
24
143
  /**
25
144
  * Find the chati-dev CLI module. Tries local node_modules first (fast),
26
145
  * then monorepo dev layout. Returns the module or null.
@@ -245,9 +364,64 @@ async function dispatchOrchestrate(subCommand, argv) {
245
364
  }
246
365
  }
247
366
 
367
+ async function runSecurityPreflight(projectDir) {
368
+ const result = { ok: true, license: null, integrity: true, error: null };
369
+ try {
370
+ const integrity = await validateProviderIntegrity(projectDir);
371
+ if (!integrity.valid) {
372
+ return {
373
+ ...result,
374
+ ok: false,
375
+ integrity: false,
376
+ error: `integrity_failed: missing ${integrity.missing.join(', ')}`,
377
+ };
378
+ }
379
+ } catch {
380
+ return { ...result, ok: false, integrity: false, error: 'integrity_failed' };
381
+ }
382
+
383
+ try {
384
+ const fwDir = existsSync(join(projectDir, '.chati.dev')) ? '.chati.dev' : 'chati.dev';
385
+ const hookPath = join(projectDir, fwDir, 'hooks', 'license-guard.js');
386
+ if (!existsSync(hookPath)) {
387
+ return {
388
+ ...result,
389
+ ok: false,
390
+ error: 'license_guard_missing',
391
+ license: { valid: false, reason: 'License enforcement file missing. Reinstall with: npx chati-dev init' },
392
+ };
393
+ }
394
+ const mod = await import(hookPath);
395
+ result.license = await mod.checkLicense();
396
+ if (!result.license.valid) return { ...result, ok: false, error: 'license_invalid' };
397
+ } catch {
398
+ return {
399
+ ...result,
400
+ ok: false,
401
+ error: 'license_check_error',
402
+ license: { valid: false, error: 'license_check_error' },
403
+ };
404
+ }
405
+
406
+ return result;
407
+ }
408
+
409
+ // Only read-only diagnosis and license recovery may run without a valid
410
+ // installation preflight. `init` mutates session state and must never bypass
411
+ // license or provider-integrity enforcement.
412
+ const PREFLIGHT_EXEMPT_SUBS = new Set(['doctor', 'wait-for-license']);
413
+
248
414
  async function main() {
249
415
  const subCommand = ROUTER_ARGS[0];
250
416
 
417
+ const preflight = subCommand && PREFLIGHT_EXEMPT_SUBS.has(subCommand)
418
+ ? { ok: true, license: null, integrity: true, error: null }
419
+ : await runSecurityPreflight(PROJECT_DIR);
420
+ if (!preflight.ok) {
421
+ console.log(JSON.stringify(preflight));
422
+ return;
423
+ }
424
+
251
425
  // Sub-command mode: `node chati-router.js <subcommand> [--flags...]`
252
426
  // Routes to the local CLI for ANY orchestrate subcommand. No npx, no network.
253
427
  if (subCommand && VALID_ORCHESTRATE_SUBS.has(subCommand)) {
@@ -271,8 +445,8 @@ async function main() {
271
445
  // No-subcommand mode (default): full router pipeline (license + integrity + session + next)
272
446
  const result = {
273
447
  ok: true,
274
- license: null,
275
- integrity: true,
448
+ license: preflight.license,
449
+ integrity: preflight.integrity,
276
450
  session: null,
277
451
  action: null,
278
452
  pipeline: null,
@@ -281,75 +455,7 @@ async function main() {
281
455
  };
282
456
 
283
457
  // -----------------------------------------------------------------------
284
- // 1. License validation
285
- // -----------------------------------------------------------------------
286
- try {
287
- const fwDir = existsSync(join(PROJECT_DIR, '.chati.dev')) ? '.chati.dev' : 'chati.dev';
288
- const hookPath = join(PROJECT_DIR, fwDir, 'hooks', 'license-guard.js');
289
- if (!existsSync(hookPath)) {
290
- result.ok = false;
291
- result.error = 'license_guard_missing';
292
- result.license = { valid: false, reason: 'License enforcement file missing. Reinstall with: npx chati-dev init' };
293
- console.log(JSON.stringify(result));
294
- return;
295
- }
296
-
297
- const mod = await import(hookPath);
298
- const license = await mod.checkLicense();
299
- result.license = license;
300
-
301
- if (!license.valid) {
302
- result.ok = false;
303
- result.error = 'license_invalid';
304
- console.log(JSON.stringify(result));
305
- return;
306
- }
307
- } catch (err) {
308
- // Fail-closed on license check errors
309
- result.license = { valid: false, error: 'license_check_error' };
310
- result.ok = false;
311
- }
312
-
313
- // -----------------------------------------------------------------------
314
- // 2. Integrity check — are all required hooks registered in settings.json?
315
- // Note: license-guard is registered under UserPromptSubmit (not PreToolUse).
316
- // mode-governance, constitution-guard, and read-protection are in PreToolUse.
317
- // -----------------------------------------------------------------------
318
- try {
319
- const settingsPath = join(PROJECT_DIR, '.claude', 'settings.json');
320
- if (existsSync(settingsPath)) {
321
- const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
322
- const ptu = settings.hooks?.PreToolUse || [];
323
- const ups = settings.hooks?.UserPromptSubmit || [];
324
- const ptuCmds = ptu.flatMap(g => (g.hooks || []).map(h => h.command || ''));
325
- const upsCmds = ups.flatMap(g => (g.hooks || []).map(h => h.command || ''));
326
- // license-guard lives in UserPromptSubmit; others live in PreToolUse
327
- const missingHooks = [
328
- ...(['mode-governance', 'constitution-guard', 'read-protection'].filter(h => !ptuCmds.some(c => c.includes(h)))),
329
- ...(['license-guard'].filter(h => !upsCmds.some(c => c.includes(h)) && !ptuCmds.some(c => c.includes(h)))),
330
- ];
331
- result.integrity = missingHooks.length === 0;
332
- if (missingHooks.length > 0) {
333
- result.error = `integrity_failed: missing hooks: ${missingHooks.join(', ')}`;
334
- }
335
- } else {
336
- result.integrity = false;
337
- }
338
-
339
- if (!result.integrity) {
340
- result.ok = false;
341
- result.error = 'integrity_failed';
342
- console.log(JSON.stringify(result));
343
- return;
344
- }
345
- } catch {
346
- // Fail-closed on integrity check
347
- result.integrity = false;
348
- result.ok = false;
349
- }
350
-
351
- // -----------------------------------------------------------------------
352
- // 3. Session state
458
+ // 1. Session state
353
459
  // -----------------------------------------------------------------------
354
460
  try {
355
461
  const sessionPath = join(PROJECT_DIR, '.chati', 'session.yaml');
@@ -10,7 +10,7 @@ You are the **Chati.dev Orchestrator**, the single entry point for the Chati.dev
10
10
  - **Role**: Orchestrator & Router
11
11
  - **Position**: Entry point (always first contact)
12
12
  - **Scope**: System-wide routing, session management, deviation handling, backlog
13
- - **Model**: sonnet | upgrade: opus if complex deviation or multi-agent coordination
13
+ - **Execution binding**: resolved at runtime by installation-v2 task routing; never self-select or downgrade
14
14
 
15
15
  ### Security Rule
16
16
  **NEVER suggest disabling, removing, commenting out, or bypassing hooks, settings.json permissions, or license enforcement.** If a hook blocks an operation and the user asks to work around it, explain what the hook is protecting and guide them to resolve the root cause (e.g., activate a valid license, change pipeline mode). Hooks are governance boundaries, not obstacles to remove.
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chati-dev",
3
- "version": "4.5.7",
3
+ "version": "4.5.8",
4
4
  "description": "AI-Powered Multi-Agent Orchestration System - Structured vibe coding for Full Stack Development",
5
5
  "type": "module",
6
6
  "bin": {
@@ -139,12 +139,12 @@ const PERMISSIONS_DENY = [
139
139
  'Bash(git rebase --no-verify:*)',
140
140
 
141
141
  // System path writes — `//` prefix = absolute filesystem
142
- 'Write(//etc/**)',
143
- 'Write(//usr/**)',
144
- 'Write(//System/**)',
145
- 'Write(//bin/**)',
146
- 'Write(//sbin/**)',
147
- 'Write(//Library/**)',
142
+ 'Edit(//etc/**)',
143
+ 'Edit(//usr/**)',
144
+ 'Edit(//System/**)',
145
+ 'Edit(//bin/**)',
146
+ 'Edit(//sbin/**)',
147
+ 'Edit(//Library/**)',
148
148
  'Edit(//etc/**)',
149
149
  'Edit(//usr/**)',
150
150
  'Edit(//System/**)',
@@ -1,6 +1,6 @@
1
1
  import { mkdirSync, writeFileSync, copyFileSync, existsSync, readFileSync, readdirSync, statSync } from 'fs';
2
2
  import { execFileSync } from 'child_process';
3
- import { join, dirname, basename } from 'path';
3
+ import { join, dirname } from 'path';
4
4
  import { fileURLToPath } from 'url';
5
5
  import { createRequire } from 'module';
6
6
  import { IDE_CONFIGS, IDE_TO_PROVIDER } from '../config/ide-configs.js';
@@ -10,6 +10,7 @@ import { generateContextFiles } from '../config/context-file-generator.js';
10
10
  import { adaptFrameworkFile, ADAPTABLE_FILES } from '../config/framework-adapter.js';
11
11
  import { generateClaudeSettings } from '../config/claude-settings-generator.js';
12
12
  import { generateProviderOverlays } from './provider-overlay.js';
13
+ import { applyInstalledPathReplacement } from './path-replacement.js';
13
14
  import { verifyManifest } from './manifest.js';
14
15
 
15
16
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -192,7 +193,7 @@ export async function installFramework(config) {
192
193
  if (existsSync(src)) {
193
194
  // Apply path replacement so rules reference .chati.dev/ not chati.dev/
194
195
  let content = readFileSync(src, 'utf-8');
195
- content = applyPathReplacement(content);
196
+ content = applyInstalledPathReplacement(content);
196
197
  writeFileSync(join(claudeRulesDir, file), content, 'utf-8');
197
198
  }
198
199
  }
@@ -269,6 +270,7 @@ export const FRAMEWORK_DIRS_TO_COPY = [
269
270
  */
270
271
  const FRAMEWORK_ROOT_FILES = [
271
272
  'constitution.md',
273
+ 'package.json',
272
274
  ];
273
275
 
274
276
  /**
@@ -287,20 +289,6 @@ const TEXT_EXTENSIONS = new Set(['.md', '.js', '.yaml', '.yml', '.json', '.html'
287
289
  * Order matters: replace `chati.dev/artifacts/` FIRST (→ `artifacts/`),
288
290
  * then `chati.dev/` (→ `.chati.dev/`). Reversing would produce `.chati.dev/artifacts/`.
289
291
  */
290
- function applyPathReplacement(content) {
291
- return content
292
- // Step 1: artifacts at root (must be before generic replace)
293
- .replace(/chati\.dev\/artifacts\//g, 'artifacts/')
294
- // Step 2: path strings with slash — but NOT if already prefixed with dot
295
- // (negative lookbehind for any char that would create `.chati.dev` already)
296
- .replace(/(?<!\.)chati\.dev\//g, '.chati.dev/')
297
- // Step 3: quoted dir name in JS join() calls (WITHOUT slash)
298
- // Only match 'chati.dev' (the DIRECTORY, with dot) not 'chati-dev' (the NPM PACKAGE, with dash)
299
- // Also: avoid double-prefixing if already '.chati.dev' in source
300
- .replace(/(?<!\.)'chati\.dev'/g, "'.chati.dev'")
301
- .replace(/(?<!\.)"chati\.dev"/g, '".chati.dev"');
302
- }
303
-
304
292
  /**
305
293
  * Recursively copy a directory from source to destination, applying:
306
294
  * 1. Path replacement (chati.dev/ → .chati.dev/, artifacts/ at root)
@@ -327,7 +315,7 @@ function copyDirRecursive(srcDir, destDir, provider, relBase = '') {
327
315
  if (isText) {
328
316
  let content = readFileSync(srcPath, 'utf-8');
329
317
  // Always apply path replacement for installed location
330
- content = applyPathReplacement(content);
318
+ content = applyInstalledPathReplacement(content);
331
319
  // Provider adaptation on top (for non-Claude)
332
320
  if (provider !== 'claude' && ADAPTABLE_FILES.has(relPath)) {
333
321
  content = adaptFrameworkFile(content, relPath, provider);
@@ -360,7 +348,7 @@ export function copyFrameworkFiles(destDir, provider = 'claude') {
360
348
  const dest = join(destDir, file);
361
349
  createDir(dirname(dest));
362
350
  let content = readFileSync(src, 'utf-8');
363
- content = applyPathReplacement(content);
351
+ content = applyInstalledPathReplacement(content);
364
352
  if (provider !== 'claude' && ADAPTABLE_FILES.has(file)) {
365
353
  content = adaptFrameworkFile(content, file, provider);
366
354
  }
@@ -448,17 +436,18 @@ Parse the JSON output. This single command already handled: license validation,
448
436
 
449
437
  // Starlark execution policies (Codex sandboxed rule engine)
450
438
  createDir(join(targetDir, '.codex', 'rules'));
439
+ writeCodexConfigWithHooks(join(targetDir, '.codex', 'config.toml'));
451
440
  writeFileSync(join(targetDir, '.codex', 'rules', 'constitution-guard.rules'), generateCodexConstitutionGuardRules(), 'utf-8');
452
441
  writeFileSync(join(targetDir, '.codex', 'rules', 'read-protection.rules'), generateCodexReadProtectionRules(), 'utf-8');
453
442
 
454
443
  // .codex/hooks.json — license-guard wired to UserPromptSubmit (per-turn enforcement).
455
- // Codex hooks.json is experimental (requires [features] codex_hooks = true in
456
- // codex config). Without it, license enforcement only happens at slash command
444
+ // Codex hooks require [features] hooks = true in the active project config.
445
+ // Without it, license enforcement only happens at skill entry and the router,
457
446
  // entry — a long-running terminal session would not be re-validated.
458
447
  writeFileSync(
459
448
  join(targetDir, '.codex', 'hooks.json'),
460
449
  JSON.stringify({
461
- $comment: 'chati.dev v4.2.2 experimental Codex hooks. Requires [features] codex_hooks = true.',
450
+ $comment: 'chati.dev Codex lifecycle hooks. Requires [features] hooks = true.',
462
451
  hooks: {
463
452
  UserPromptSubmit: [
464
453
  {
@@ -486,7 +475,7 @@ Parse the JSON output. This single command already handled: license validation,
486
475
  for (const file of contextFileNames) {
487
476
  const src = join(FRAMEWORK_SOURCE, 'context', file);
488
477
  if (existsSync(src)) {
489
- const content = readFileSync(src, 'utf-8');
478
+ const content = applyInstalledPathReplacement(readFileSync(src, 'utf-8'));
490
479
  writeFileSync(join(geminiContextDir, file), adaptFrameworkFile(content, `context/${file}`, 'gemini'), 'utf-8');
491
480
  }
492
481
  }
@@ -515,6 +504,38 @@ Parse the JSON output. This single command already handled: license validation,
515
504
  }
516
505
  }
517
506
 
507
+ export function writeCodexConfigWithHooks(configPath) {
508
+ createDir(dirname(configPath));
509
+ if (!existsSync(configPath)) {
510
+ writeFileSync(configPath, '[features]\nhooks = true\n', 'utf-8');
511
+ return;
512
+ }
513
+
514
+ const original = readFileSync(configPath, 'utf-8');
515
+ if (/^\s*features\.hooks\s*=\s*true\s*(?:#.*)?$/m.test(original)) return;
516
+ if (/^\s*features\.hooks\s*=/m.test(original)) {
517
+ writeFileSync(configPath, original.replace(/^\s*features\.hooks\s*=.*$/m, 'features.hooks = true'), 'utf-8');
518
+ return;
519
+ }
520
+
521
+ const lines = original.split('\n');
522
+ const sectionStart = lines.findIndex((line) => /^\s*\[features\]\s*(?:#.*)?$/.test(line));
523
+ if (sectionStart === -1) {
524
+ const prefix = original.endsWith('\n') ? original : `${original}\n`;
525
+ writeFileSync(configPath, `${prefix}\n[features]\nhooks = true\n`, 'utf-8');
526
+ return;
527
+ }
528
+
529
+ let sectionEnd = lines.findIndex((line, index) => index > sectionStart && /^\s*\[.*\]\s*(?:#.*)?$/.test(line));
530
+ if (sectionEnd === -1) sectionEnd = lines.length;
531
+ const hookIndex = lines.findIndex((line, index) =>
532
+ index > sectionStart && index < sectionEnd && /^\s*hooks\s*=/.test(line)
533
+ );
534
+ if (hookIndex !== -1) lines[hookIndex] = 'hooks = true';
535
+ else lines.splice(sectionEnd, 0, 'hooks = true');
536
+ writeFileSync(configPath, lines.join('\n'), 'utf-8');
537
+ }
538
+
518
539
  /**
519
540
  * Generate provider-agnostic instructions file content.
520
541
  * Used for non-Claude IDEs (.vscode/chati/rules.md, .cursorrules, etc.)
@@ -826,9 +847,14 @@ export function copyCliDependencies(pkgDir, destNodeModules) {
826
847
  let p = require.resolve(depName);
827
848
  while (p && p !== dirname(p)) {
828
849
  p = dirname(p);
829
- if (existsSync(join(p, 'package.json')) && basename(p) === depName.split('/').pop()) {
830
- depDir = p;
831
- break;
850
+ const candidatePackage = join(p, 'package.json');
851
+ if (existsSync(candidatePackage)) {
852
+ try {
853
+ if (JSON.parse(readFileSync(candidatePackage, 'utf-8')).name === depName) {
854
+ depDir = p;
855
+ break;
856
+ }
857
+ } catch { /* keep walking */ }
832
858
  }
833
859
  }
834
860
  } catch { continue; }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Translate repository paths into their installed-project locations.
3
+ *
4
+ * Source files live under `chati.dev/`. Installed framework files live under
5
+ * `.chati.dev/`, while user-facing artifacts remain at project root.
6
+ */
7
+ export function applyInstalledPathReplacement(content) {
8
+ return content
9
+ .replace(/chati\.dev\/artifacts\//g, 'artifacts/')
10
+ .replace(/(?<!\.)chati\.dev\//g, '.chati.dev/')
11
+ .replace(/(?<!\.)'chati\.dev'/g, "'.chati.dev'")
12
+ .replace(/(?<!\.)"chati\.dev"/g, '".chati.dev"');
13
+ }
@@ -13,6 +13,7 @@
13
13
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
14
14
  import { join, dirname } from 'path';
15
15
  import { adaptFrameworkFile, ADAPTABLE_FILES } from '../config/framework-adapter.js';
16
+ import { applyInstalledPathReplacement } from './path-replacement.js';
16
17
 
17
18
  /**
18
19
  * Generate adapted framework overlays for all enabled providers.
@@ -41,7 +42,8 @@ export function generateProviderOverlays(targetDir, frameworkSource, allProvider
41
42
 
42
43
  // Read CANONICAL (Claude) content from source, then adapt for target provider
43
44
  const canonicalContent = readFileSync(srcPath, 'utf-8');
44
- const adapted = adaptFrameworkFile(canonicalContent, file, provider);
45
+ const installedContent = applyInstalledPathReplacement(canonicalContent);
46
+ const adapted = adaptFrameworkFile(installedContent, file, provider);
45
47
 
46
48
  const destPath = join(overlayDir, file);
47
49
  mkdirSync(dirname(destPath), { recursive: true });
@@ -35,6 +35,54 @@ function assertString(value, code, label) {
35
35
  if (typeof value !== 'string' || value.trim() === '') throw new ContractError(code, `${label} must be a non-empty string`);
36
36
  }
37
37
 
38
+ function assertExactKeys(value, allowedKeys, label) {
39
+ assertObject(value, 'INVALID_ARTIFACT', label);
40
+ const unexpected = Object.keys(value).filter((key) => !allowedKeys.has(key));
41
+ if (unexpected.length > 0) {
42
+ throw new ContractError('UNEXPECTED_ARTIFACT_FIELD', `${label} contains unsupported fields: ${unexpected.join(', ')}`);
43
+ }
44
+ }
45
+
46
+ const ARTIFACT_KEYS = new Set([
47
+ 'schema_version', 'artifact_type', 'project_id', 'created_at', 'installation',
48
+ 'capability_snapshot', 'brain_read_capability', 'managed_paths',
49
+ 'installation_artifact_id', 'digest',
50
+ ]);
51
+ const INSTALLATION_KEYS = new Set([
52
+ 'installation_id', 'profile', 'enabled_providers', 'capability_snapshot_ref',
53
+ 'policy_ref', 'primary_binding', 'primary_harness', 'external_integrations',
54
+ ]);
55
+ const BINDING_KEYS = new Set([
56
+ 'provider_id', 'harness_id', 'allowed_models', 'allowed_reasoning_configurations',
57
+ ]);
58
+ const SNAPSHOT_KEYS = new Set(['snapshot_id', 'expires_at', 'models']);
59
+ const MODEL_KEYS = new Set([
60
+ 'provider_id', 'model_id', 'actions', 'adjudication_priority',
61
+ 'highest_reasoning_configuration', 'routing_priority', 'tier', 'natural_role',
62
+ ]);
63
+
64
+ function assertClosedArtifactSchema(artifact) {
65
+ assertExactKeys(artifact, ARTIFACT_KEYS, 'artifact');
66
+ assertExactKeys(artifact.installation, INSTALLATION_KEYS, 'installation');
67
+ for (const binding of artifact.installation.enabled_providers ?? []) {
68
+ assertExactKeys(binding, BINDING_KEYS, 'enabled provider binding');
69
+ }
70
+ if (artifact.installation.primary_binding !== undefined) {
71
+ assertExactKeys(artifact.installation.primary_binding, new Set(['provider_id', 'harness_id']), 'primary_binding');
72
+ }
73
+ if (artifact.installation.external_integrations !== undefined) {
74
+ assertExactKeys(artifact.installation.external_integrations, new Set(['clickup']), 'external_integrations');
75
+ }
76
+ assertExactKeys(artifact.capability_snapshot, SNAPSHOT_KEYS, 'capability_snapshot');
77
+ for (const model of artifact.capability_snapshot.models ?? []) {
78
+ assertExactKeys(model, MODEL_KEYS, 'capability model');
79
+ }
80
+ const brainKeys = artifact.brain_read_capability?.status === 'configured'
81
+ ? new Set(['status', 'mode', 'policy_ref'])
82
+ : new Set(['status']);
83
+ assertExactKeys(artifact.brain_read_capability, brainKeys, 'brain_read_capability');
84
+ }
85
+
38
86
  function readClock(clock) {
39
87
  if (typeof clock !== 'function') throw new ContractError('MISSING_CLOCK', 'a deterministic clock function is required');
40
88
  const result = clock();
@@ -127,6 +175,7 @@ export function doctorV2(artifact, { clock = () => new Date() } = {}) {
127
175
  const checks = [];
128
176
  try {
129
177
  assertObject(artifact, 'INVALID_ARTIFACT', 'artifact');
178
+ assertClosedArtifactSchema(artifact);
130
179
  if (artifact.schema_version !== INSTALLATION_SCHEMA_VERSION || artifact.artifact_type !== 'chati-installation') {
131
180
  throw new ContractError('UNSUPPORTED_INSTALLATION_ARTIFACT', 'expected a chati installation artifact v2');
132
181
  }
@@ -138,6 +187,12 @@ export function doctorV2(artifact, { clock = () => new Date() } = {}) {
138
187
  if (!Array.isArray(artifact.managed_paths) || !artifact.managed_paths.every((path) => ensureRelativePath(path))) {
139
188
  throw new ContractError('INVALID_MANAGED_PATH', 'managed_paths must be valid project-relative paths');
140
189
  }
190
+ if (!artifact.managed_paths.includes(INSTALLATION_ARTIFACT_PATH)) {
191
+ throw new ContractError('INVALID_MANAGED_PATH', `managed_paths must include ${INSTALLATION_ARTIFACT_PATH}`);
192
+ }
193
+ if (new Set(artifact.managed_paths).size !== artifact.managed_paths.length) {
194
+ throw new ContractError('DUPLICATE_MANAGED_PATH', 'managed_paths must be unique');
195
+ }
141
196
  checks.push({ name: 'artifact', status: 'pass', message: 'artifact schema and digest are valid' });
142
197
  checks.push({ name: 'selected-bindings', status: 'pass', message: 'only explicitly selected provider/harness bindings are present' });
143
198
  checks.push({ name: 'brain-capability', status: 'pass', message: 'Brain capability is absent or read-only' });