mindforge-cc 11.2.0 → 11.3.0

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 (213) hide show
  1. package/.mindforge/config.json +3 -2
  2. package/.mindforge/imported-agents.jsonl +154 -0
  3. package/CHANGELOG.md +80 -1
  4. package/MINDFORGE.md +5 -5
  5. package/README.md +1 -1
  6. package/bin/autonomous/mesh-self-healer.js +101 -28
  7. package/bin/browser/regression-writer.js +45 -3
  8. package/bin/browser/session-manager.js +21 -17
  9. package/bin/engine/logic-drift-detector.js +14 -6
  10. package/bin/engine/logic-validator.js +155 -25
  11. package/bin/engine/orbital-guardian.js +56 -10
  12. package/bin/engine/reason-source-aligner.js +19 -6
  13. package/bin/engine/remediation-engine.js +1 -1
  14. package/bin/engine/self-corrective-synthesizer.js +1 -1
  15. package/bin/engine/sre-manager.js +33 -6
  16. package/bin/governance/policy-engine.js +17 -4
  17. package/bin/governance/ztai-archiver.js +74 -9
  18. package/bin/governance/ztai-manager.js +3 -3
  19. package/bin/installer-core.js +126 -3
  20. package/bin/memory/eis-client.js +45 -4
  21. package/bin/memory/vector-hub.js +32 -0
  22. package/bin/review/finding-synthesizer.js +35 -6
  23. package/bin/security/trust-boundaries.js +96 -4
  24. package/bin/security/trust-gate-hook.js +13 -3
  25. package/bin/skill-registry.js +31 -20
  26. package/bin/spawn-agent.js +80 -1
  27. package/bin/sre/shadow-mirror.js +90 -40
  28. package/bin/utils/append-queue.js +12 -0
  29. package/bin/utils/file-io.js +4 -45
  30. package/bin/utils/version-check.js +21 -5
  31. package/bin/wizard/theme.js +4 -3
  32. package/package.json +3 -1
  33. package/subagents/.claude-plugin/marketplace.json +93 -0
  34. package/subagents/categories/01-core-development/.claude-plugin/plugin.json +24 -0
  35. package/subagents/categories/01-core-development/README.md +146 -0
  36. package/subagents/categories/01-core-development/api-designer-cc.md +237 -0
  37. package/subagents/categories/01-core-development/backend-developer.md +222 -0
  38. package/subagents/categories/01-core-development/design-bridge.md +129 -0
  39. package/subagents/categories/01-core-development/electron-pro.md +240 -0
  40. package/subagents/categories/01-core-development/frontend-developer.md +133 -0
  41. package/subagents/categories/01-core-development/fullstack-developer.md +235 -0
  42. package/subagents/categories/01-core-development/graphql-architect.md +238 -0
  43. package/subagents/categories/01-core-development/microservices-architect.md +239 -0
  44. package/subagents/categories/01-core-development/mobile-developer.md +283 -0
  45. package/subagents/categories/01-core-development/ui-designer.md +174 -0
  46. package/subagents/categories/01-core-development/websocket-engineer.md +150 -0
  47. package/subagents/categories/02-language-specialists/.claude-plugin/plugin.json +43 -0
  48. package/subagents/categories/02-language-specialists/README.md +245 -0
  49. package/subagents/categories/02-language-specialists/angular-architect.md +287 -0
  50. package/subagents/categories/02-language-specialists/cpp-pro.md +277 -0
  51. package/subagents/categories/02-language-specialists/csharp-developer.md +287 -0
  52. package/subagents/categories/02-language-specialists/django-developer.md +287 -0
  53. package/subagents/categories/02-language-specialists/dotnet-core-expert.md +287 -0
  54. package/subagents/categories/02-language-specialists/dotnet-framework-48-expert.md +306 -0
  55. package/subagents/categories/02-language-specialists/elixir-expert.md +311 -0
  56. package/subagents/categories/02-language-specialists/expo-react-native-expert.md +268 -0
  57. package/subagents/categories/02-language-specialists/fastapi-developer.md +287 -0
  58. package/subagents/categories/02-language-specialists/flutter-expert.md +287 -0
  59. package/subagents/categories/02-language-specialists/golang-pro.md +277 -0
  60. package/subagents/categories/02-language-specialists/java-architect.md +287 -0
  61. package/subagents/categories/02-language-specialists/javascript-pro.md +277 -0
  62. package/subagents/categories/02-language-specialists/kotlin-specialist.md +287 -0
  63. package/subagents/categories/02-language-specialists/laravel-specialist.md +287 -0
  64. package/subagents/categories/02-language-specialists/nextjs-developer.md +287 -0
  65. package/subagents/categories/02-language-specialists/node-specialist.md +124 -0
  66. package/subagents/categories/02-language-specialists/php-pro.md +287 -0
  67. package/subagents/categories/02-language-specialists/powershell-51-expert.md +59 -0
  68. package/subagents/categories/02-language-specialists/powershell-7-expert.md +57 -0
  69. package/subagents/categories/02-language-specialists/python-pro.md +277 -0
  70. package/subagents/categories/02-language-specialists/rails-expert.md +358 -0
  71. package/subagents/categories/02-language-specialists/react-specialist-cc.md +287 -0
  72. package/subagents/categories/02-language-specialists/rust-engineer.md +287 -0
  73. package/subagents/categories/02-language-specialists/spring-boot-engineer.md +287 -0
  74. package/subagents/categories/02-language-specialists/sql-pro.md +287 -0
  75. package/subagents/categories/02-language-specialists/swift-expert.md +287 -0
  76. package/subagents/categories/02-language-specialists/symfony-specialist.md +354 -0
  77. package/subagents/categories/02-language-specialists/typescript-pro.md +277 -0
  78. package/subagents/categories/02-language-specialists/vue-expert.md +287 -0
  79. package/subagents/categories/03-infrastructure/.claude-plugin/plugin.json +29 -0
  80. package/subagents/categories/03-infrastructure/README.md +170 -0
  81. package/subagents/categories/03-infrastructure/azure-infra-engineer.md +53 -0
  82. package/subagents/categories/03-infrastructure/cloud-architect-cc.md +277 -0
  83. package/subagents/categories/03-infrastructure/database-administrator.md +287 -0
  84. package/subagents/categories/03-infrastructure/deployment-engineer.md +287 -0
  85. package/subagents/categories/03-infrastructure/devops-engineer-cc.md +287 -0
  86. package/subagents/categories/03-infrastructure/devops-incident-responder.md +287 -0
  87. package/subagents/categories/03-infrastructure/docker-expert.md +278 -0
  88. package/subagents/categories/03-infrastructure/incident-responder.md +287 -0
  89. package/subagents/categories/03-infrastructure/kubernetes-specialist.md +287 -0
  90. package/subagents/categories/03-infrastructure/network-engineer.md +287 -0
  91. package/subagents/categories/03-infrastructure/platform-engineer-cc.md +287 -0
  92. package/subagents/categories/03-infrastructure/security-engineer.md +277 -0
  93. package/subagents/categories/03-infrastructure/sre-engineer.md +287 -0
  94. package/subagents/categories/03-infrastructure/terraform-engineer.md +287 -0
  95. package/subagents/categories/03-infrastructure/terragrunt-expert.md +307 -0
  96. package/subagents/categories/03-infrastructure/windows-infra-admin.md +52 -0
  97. package/subagents/categories/04-quality-security/.claude-plugin/plugin.json +30 -0
  98. package/subagents/categories/04-quality-security/README.md +175 -0
  99. package/subagents/categories/04-quality-security/accessibility-tester-cc.md +277 -0
  100. package/subagents/categories/04-quality-security/ad-security-reviewer.md +56 -0
  101. package/subagents/categories/04-quality-security/ai-writing-auditor.md +77 -0
  102. package/subagents/categories/04-quality-security/architect-reviewer.md +287 -0
  103. package/subagents/categories/04-quality-security/chaos-engineer-cc.md +277 -0
  104. package/subagents/categories/04-quality-security/code-reviewer.md +287 -0
  105. package/subagents/categories/04-quality-security/compliance-auditor-cc.md +277 -0
  106. package/subagents/categories/04-quality-security/debugger-cc.md +287 -0
  107. package/subagents/categories/04-quality-security/error-detective.md +287 -0
  108. package/subagents/categories/04-quality-security/gdpr-ccpa-compliance.md +98 -0
  109. package/subagents/categories/04-quality-security/penetration-tester.md +287 -0
  110. package/subagents/categories/04-quality-security/performance-engineer.md +287 -0
  111. package/subagents/categories/04-quality-security/powershell-security-hardening.md +54 -0
  112. package/subagents/categories/04-quality-security/qa-expert.md +287 -0
  113. package/subagents/categories/04-quality-security/security-auditor.md +287 -0
  114. package/subagents/categories/04-quality-security/test-automator.md +287 -0
  115. package/subagents/categories/04-quality-security/ui-ux-tester.md +234 -0
  116. package/subagents/categories/05-data-ai/.claude-plugin/plugin.json +26 -0
  117. package/subagents/categories/05-data-ai/README.md +153 -0
  118. package/subagents/categories/05-data-ai/ai-engineer.md +287 -0
  119. package/subagents/categories/05-data-ai/data-analyst.md +277 -0
  120. package/subagents/categories/05-data-ai/data-engineer-cc.md +287 -0
  121. package/subagents/categories/05-data-ai/data-scientist.md +287 -0
  122. package/subagents/categories/05-data-ai/database-optimizer.md +287 -0
  123. package/subagents/categories/05-data-ai/llm-architect.md +287 -0
  124. package/subagents/categories/05-data-ai/machine-learning-engineer.md +277 -0
  125. package/subagents/categories/05-data-ai/ml-engineer-cc.md +287 -0
  126. package/subagents/categories/05-data-ai/mlops-engineer.md +287 -0
  127. package/subagents/categories/05-data-ai/nlp-engineer.md +287 -0
  128. package/subagents/categories/05-data-ai/postgres-pro.md +287 -0
  129. package/subagents/categories/05-data-ai/prompt-engineer-cc.md +287 -0
  130. package/subagents/categories/05-data-ai/reinforcement-learning-engineer.md +277 -0
  131. package/subagents/categories/06-developer-experience/.claude-plugin/plugin.json +28 -0
  132. package/subagents/categories/06-developer-experience/README.md +157 -0
  133. package/subagents/categories/06-developer-experience/build-engineer-cc.md +286 -0
  134. package/subagents/categories/06-developer-experience/cli-developer.md +286 -0
  135. package/subagents/categories/06-developer-experience/dependency-manager.md +286 -0
  136. package/subagents/categories/06-developer-experience/documentation-engineer.md +276 -0
  137. package/subagents/categories/06-developer-experience/dx-optimizer.md +286 -0
  138. package/subagents/categories/06-developer-experience/git-workflow-manager.md +286 -0
  139. package/subagents/categories/06-developer-experience/legacy-modernizer.md +286 -0
  140. package/subagents/categories/06-developer-experience/mcp-developer.md +275 -0
  141. package/subagents/categories/06-developer-experience/powershell-module-architect.md +58 -0
  142. package/subagents/categories/06-developer-experience/powershell-ui-architect.md +135 -0
  143. package/subagents/categories/06-developer-experience/readme-generator.md +238 -0
  144. package/subagents/categories/06-developer-experience/refactoring-specialist.md +286 -0
  145. package/subagents/categories/06-developer-experience/slack-expert.md +232 -0
  146. package/subagents/categories/06-developer-experience/tooling-engineer.md +286 -0
  147. package/subagents/categories/06-developer-experience/visual-asset-generator.md +34 -0
  148. package/subagents/categories/07-specialized-domains/.claude-plugin/plugin.json +27 -0
  149. package/subagents/categories/07-specialized-domains/README.md +161 -0
  150. package/subagents/categories/07-specialized-domains/api-documenter.md +277 -0
  151. package/subagents/categories/07-specialized-domains/blockchain-developer.md +287 -0
  152. package/subagents/categories/07-specialized-domains/embedded-systems.md +287 -0
  153. package/subagents/categories/07-specialized-domains/fintech-engineer.md +287 -0
  154. package/subagents/categories/07-specialized-domains/game-developer.md +287 -0
  155. package/subagents/categories/07-specialized-domains/healthcare-admin.md +199 -0
  156. package/subagents/categories/07-specialized-domains/hipaa-compliance.md +112 -0
  157. package/subagents/categories/07-specialized-domains/iot-engineer.md +287 -0
  158. package/subagents/categories/07-specialized-domains/m365-admin.md +48 -0
  159. package/subagents/categories/07-specialized-domains/mobile-app-developer.md +287 -0
  160. package/subagents/categories/07-specialized-domains/payment-integration.md +287 -0
  161. package/subagents/categories/07-specialized-domains/quant-analyst.md +287 -0
  162. package/subagents/categories/07-specialized-domains/risk-manager.md +287 -0
  163. package/subagents/categories/07-specialized-domains/seo-specialist-cc.md +184 -0
  164. package/subagents/categories/08-business-product/.claude-plugin/plugin.json +29 -0
  165. package/subagents/categories/08-business-product/README.md +160 -0
  166. package/subagents/categories/08-business-product/assumption-mapping.md +77 -0
  167. package/subagents/categories/08-business-product/backlog-grooming.md +88 -0
  168. package/subagents/categories/08-business-product/business-analyst-cc.md +287 -0
  169. package/subagents/categories/08-business-product/content-marketer.md +287 -0
  170. package/subagents/categories/08-business-product/content-quality-editor.md +55 -0
  171. package/subagents/categories/08-business-product/customer-success-manager.md +287 -0
  172. package/subagents/categories/08-business-product/growth-loops.md +91 -0
  173. package/subagents/categories/08-business-product/legal-advisor.md +287 -0
  174. package/subagents/categories/08-business-product/license-engineer.md +295 -0
  175. package/subagents/categories/08-business-product/product-manager-cc.md +287 -0
  176. package/subagents/categories/08-business-product/project-manager.md +287 -0
  177. package/subagents/categories/08-business-product/sales-engineer.md +287 -0
  178. package/subagents/categories/08-business-product/scrum-master.md +287 -0
  179. package/subagents/categories/08-business-product/technical-writer.md +287 -0
  180. package/subagents/categories/08-business-product/ux-researcher.md +287 -0
  181. package/subagents/categories/08-business-product/wordpress-master.md +316 -0
  182. package/subagents/categories/09-meta-orchestration/.claude-plugin/plugin.json +24 -0
  183. package/subagents/categories/09-meta-orchestration/README.md +140 -0
  184. package/subagents/categories/09-meta-orchestration/agent-installer.md +97 -0
  185. package/subagents/categories/09-meta-orchestration/agent-organizer.md +287 -0
  186. package/subagents/categories/09-meta-orchestration/codebase-orchestrator.md +249 -0
  187. package/subagents/categories/09-meta-orchestration/context-manager.md +287 -0
  188. package/subagents/categories/09-meta-orchestration/error-coordinator.md +287 -0
  189. package/subagents/categories/09-meta-orchestration/it-ops-orchestrator.md +60 -0
  190. package/subagents/categories/09-meta-orchestration/knowledge-synthesizer.md +287 -0
  191. package/subagents/categories/09-meta-orchestration/multi-agent-coordinator.md +287 -0
  192. package/subagents/categories/09-meta-orchestration/performance-monitor.md +287 -0
  193. package/subagents/categories/09-meta-orchestration/task-distributor.md +287 -0
  194. package/subagents/categories/09-meta-orchestration/workflow-orchestrator.md +287 -0
  195. package/subagents/categories/10-research-analysis/.claude-plugin/plugin.json +24 -0
  196. package/subagents/categories/10-research-analysis/README.md +141 -0
  197. package/subagents/categories/10-research-analysis/ab-test-analysis.md +101 -0
  198. package/subagents/categories/10-research-analysis/cohort-analysis.md +100 -0
  199. package/subagents/categories/10-research-analysis/competitive-analyst.md +287 -0
  200. package/subagents/categories/10-research-analysis/data-researcher.md +287 -0
  201. package/subagents/categories/10-research-analysis/first-principles-thinking.md +100 -0
  202. package/subagents/categories/10-research-analysis/market-researcher.md +287 -0
  203. package/subagents/categories/10-research-analysis/project-idea-validator.md +269 -0
  204. package/subagents/categories/10-research-analysis/research-analyst.md +287 -0
  205. package/subagents/categories/10-research-analysis/scientific-literature-researcher.md +151 -0
  206. package/subagents/categories/10-research-analysis/search-specialist.md +287 -0
  207. package/subagents/categories/10-research-analysis/trend-analyst.md +287 -0
  208. package/subagents/tools/subagent-catalog/README.md +58 -0
  209. package/subagents/tools/subagent-catalog/config.sh +94 -0
  210. package/subagents/tools/subagent-catalog/fetch.md +82 -0
  211. package/subagents/tools/subagent-catalog/invalidate.md +47 -0
  212. package/subagents/tools/subagent-catalog/list.md +54 -0
  213. package/subagents/tools/subagent-catalog/search.md +58 -0
@@ -20,19 +20,35 @@ class ZTAIArchiver {
20
20
  * @param {string} archiverDid - The DID of the archiver (e.g., Release Manager)
21
21
  * @returns {Promise<Object>} - The signed manifest
22
22
  */
23
- async generateManifest(entries, archiverDid) {
24
- if (!entries || entries.length === 0) return null;
25
-
26
- // 1. Calculate the Merkle-like root hash of the block
27
- const blockHashes = entries.map(e =>
23
+ /**
24
+ * Computes the cumulative-hash-chain "Merkle root" for a block of entries.
25
+ *
26
+ * This is the SINGLE source of truth for the root algorithm. Both
27
+ * generateManifest() (write path) and verifyIntegrity() (read/verify path)
28
+ * MUST call this so the two can never drift — a drift would re-introduce the
29
+ * false-assurance defect (audit finding #15 / UC-22).
30
+ *
31
+ * @param {Array<Object>} entries - Ordered block of audit entries.
32
+ * @returns {string} - The cumulative SHA-256 hash chain (hex).
33
+ */
34
+ _computeMerkleRoot(entries) {
35
+ const blockHashes = entries.map(e =>
28
36
  crypto.createHash('sha256').update(JSON.stringify(e)).digest('hex')
29
37
  );
30
-
31
- // Simple cumulative hash chain as a Merkle Root equivalent
38
+
39
+ // Simple cumulative hash chain as a Merkle Root equivalent.
32
40
  let cumulativeHash = '';
33
41
  for (const h of blockHashes) {
34
42
  cumulativeHash = crypto.createHash('sha256').update(cumulativeHash + h).digest('hex');
35
43
  }
44
+ return cumulativeHash;
45
+ }
46
+
47
+ async generateManifest(entries, archiverDid) {
48
+ if (!entries || entries.length === 0) return null;
49
+
50
+ // 1. Calculate the Merkle-like root hash of the block.
51
+ const cumulativeHash = this._computeMerkleRoot(entries);
36
52
 
37
53
  const manifestMetadata = {
38
54
  blockStart: entries[0].timestamp,
@@ -94,8 +110,57 @@ class ZTAIArchiver {
94
110
  throw new Error(`CRITICAL: Manifest signature invalid for ${manifestPath}`);
95
111
  }
96
112
 
97
- // 2. Recalculate and Verify Merkle Root (Simulated)
98
- // In a real environment, this would compare against the actual AUDIT.jsonl data slices.
113
+ // 2. Recalculate and Verify Merkle Root against the LIVE AUDIT.jsonl.
114
+ //
115
+ // A valid signature only proves the manifest itself is authentic; it says
116
+ // NOTHING about whether the underlying audit log still matches. We must
117
+ // recompute the root from disk and compare — anything less is false
118
+ // assurance (audit finding #15 / UC-22). Fail-closed on every error path:
119
+ // a missing/unreadable log MUST NOT pass.
120
+ let auditData;
121
+ try {
122
+ auditData = await fs.readFile(this.auditPath, 'utf8');
123
+ } catch (err) {
124
+ throw new Error(`CRITICAL: Audit log unreadable at ${this.auditPath} — cannot verify integrity (${err.message})`);
125
+ }
126
+
127
+ let allEntries;
128
+ try {
129
+ allEntries = auditData
130
+ .split('\n')
131
+ .filter(l => l.trim() !== '')
132
+ .map(l => JSON.parse(l));
133
+ } catch (err) {
134
+ throw new Error(`CRITICAL: Audit log corrupted / unparseable for ${manifestPath} (${err.message})`);
135
+ }
136
+
137
+ // Select the block this manifest covers: entries whose timestamp falls
138
+ // within [blockStart, blockEnd] inclusive.
139
+ const start = Date.parse(manifest.blockStart);
140
+ const end = Date.parse(manifest.blockEnd);
141
+ const blockEntries = allEntries.filter(e => {
142
+ const ts = Date.parse(e.timestamp);
143
+ return ts >= start && ts <= end;
144
+ });
145
+
146
+ // Sanity-check the selected count against the manifest. A mismatch means
147
+ // entries were added or deleted within the covered window.
148
+ if (blockEntries.length !== manifest.entryCount) {
149
+ throw new Error(
150
+ 'CRITICAL: Audit log tampering detected — block entry count mismatch ' +
151
+ `(manifest=${manifest.entryCount}, found=${blockEntries.length}) for ${manifestPath}`
152
+ );
153
+ }
154
+
155
+ // Recompute the root with the SAME algorithm used at archive time.
156
+ const recomputedRoot = this._computeMerkleRoot(blockEntries);
157
+ if (recomputedRoot !== manifest.merkleRoot) {
158
+ throw new Error(
159
+ `CRITICAL: Audit log tampering detected — Merkle root mismatch for ${manifestPath} ` +
160
+ `(expected=${manifest.merkleRoot}, recomputed=${recomputedRoot}).`
161
+ );
162
+ }
163
+
99
164
  console.log(`[ZTAI-ARCHIVER] Integrity Verified for block ending ${manifest.blockEnd}`);
100
165
  return true;
101
166
  }
@@ -65,7 +65,7 @@ class SecureEnclaveProvider extends KeyProvider {
65
65
  }
66
66
 
67
67
  async generate(did) {
68
- console.log(`[ZTAI-HSM] Provisioning protected identity enclave for ${did}...`);
68
+ console.log(`[ZTAI-HSM-SIM] Provisioning simulated (in-process) identity enclave for ${did}...`);
69
69
  const { publicKey, privateKey } = await generateKeyPair('ed25519');
70
70
  const pubPEM = publicKey.export({ type: 'spki', format: 'pem' });
71
71
 
@@ -82,7 +82,7 @@ class SecureEnclaveProvider extends KeyProvider {
82
82
  const record = this.enclaveStore.get(did);
83
83
  if (!record) throw new Error(`Enclave record not found for ${did}`);
84
84
 
85
- console.log(`[ZTAI-HSM] Delegating signature to hardware enclave [DID: ${did}]`);
85
+ console.log(`[ZTAI-HSM-SIM] Signing via simulated in-process enclave (NOT a hardware HSM/TPM) [DID: ${did}]`);
86
86
 
87
87
  // Simulate enclave "wrapping" or "sealing" logic
88
88
  const signature = crypto.sign(null, Buffer.from(data), record.privateKey);
@@ -93,7 +93,7 @@ class SecureEnclaveProvider extends KeyProvider {
93
93
  }
94
94
 
95
95
  async rotate(did) {
96
- console.log(`[ZTAI-HSM] Rotating enclave keys for ${did}...`);
96
+ console.log(`[ZTAI-HSM-SIM] Rotating simulated enclave keys for ${did}...`);
97
97
  return this.generate(did);
98
98
  }
99
99
 
@@ -28,6 +28,7 @@ const RUNTIMES = {
28
28
  docsSubdir: 'docs',
29
29
  memorySubdir: 'memory',
30
30
  pluginsSubdir: 'plugins',
31
+ agentsSubdir: 'agents',
31
32
  },
32
33
  antigravity: {
33
34
  displayName: 'Antigravity',
@@ -95,6 +96,25 @@ const RUNTIMES = {
95
96
  },
96
97
  };
97
98
 
99
+ /**
100
+ * Reads the target project's experimental.pqc_demo flag — the SINGLE gate that
101
+ * the engine (bin/governance/quantum-crypto.js) uses to enable the simulated
102
+ * PQAS minter. Defaults to false (engine default) when the config is absent or
103
+ * unreadable, so the installer never over-claims that PQAS is enabled.
104
+ * @param {string} cwd - Target project root being installed into.
105
+ * @returns {boolean} - true only when experimental.pqc_demo === true.
106
+ */
107
+ function isPqcDemoEnabled(cwd) {
108
+ try {
109
+ const cfgPath = path.join(cwd, '.mindforge', 'config.json');
110
+ if (!fs.existsSync(cfgPath)) return false;
111
+ const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
112
+ return cfg && cfg.experimental && cfg.experimental.pqc_demo === true;
113
+ } catch {
114
+ return false;
115
+ }
116
+ }
117
+
98
118
  /**
99
119
  * Generates runtime-specific entry file content.
100
120
  * e.g. replacing "Claude" with "Gemini" in GEMINI.md
@@ -184,6 +204,50 @@ const fsu = {
184
204
  },
185
205
  };
186
206
 
207
+ /**
208
+ * Flatten-copy the imported Claude-Code subagents into a runtime's native agents
209
+ * directory. The 154 source files live under subagents/categories/NN-name/*.md;
210
+ * Claude Code auto-discovers agents from the top level of .claude/agents/, so we
211
+ * flatten (basenames are already collision-free after the -cc renames) and skip
212
+ * the per-category README.md index files. Returns the count installed.
213
+ * @param {string} agentsDir - Destination agents directory (absolute).
214
+ * @param {object} options - { noOverwrite }.
215
+ * @returns {number} number of agent files copied.
216
+ */
217
+ function installSubagents(agentsDir, options = {}) {
218
+ const { noOverwrite = false } = options;
219
+ const sourceDir = src('subagents', 'categories');
220
+ if (!fsu.exists(sourceDir)) return 0;
221
+
222
+ fsu.ensureDir(agentsDir);
223
+ let count = 0;
224
+ for (const file of fsu.listFilesRecursive(sourceDir, '.md')) {
225
+ if (path.basename(file) === 'README.md') continue;
226
+ const dst = path.join(agentsDir, path.basename(file));
227
+ if (noOverwrite && fsu.exists(dst)) continue;
228
+ fsu.copy(file, dst);
229
+ count++;
230
+ }
231
+ return count;
232
+ }
233
+
234
+ /**
235
+ * The set of imported-subagent basenames (e.g. 'api-designer-cc.md'). Used by
236
+ * uninstall to remove ONLY our agents from a runtime's agents/ dir, never the
237
+ * user's own hand-authored agents that may live alongside them.
238
+ * @returns {Set<string>}
239
+ */
240
+ function importedSubagentBasenames() {
241
+ const sourceDir = src('subagents', 'categories');
242
+ const names = new Set();
243
+ if (!fsu.exists(sourceDir)) return names;
244
+ for (const file of fsu.listFilesRecursive(sourceDir, '.md')) {
245
+ const base = path.basename(file);
246
+ if (base !== 'README.md') names.add(base);
247
+ }
248
+ return names;
249
+ }
250
+
187
251
  // ── Registry Management ────────────────────────────────────────────────────────
188
252
  const RegistryManager = {
189
253
  getRegistryPath: () => path.join(os.homedir(), '.mindforge', 'registry.json'),
@@ -396,6 +460,13 @@ async function install(runtime, scope, options = {}) {
396
460
  }
397
461
  }
398
462
  });
463
+
464
+ if (cfg.agentsSubdir && fsu.exists(src('subagents', 'categories'))) {
465
+ const agentCount = fsu.listFilesRecursive(src('subagents', 'categories'), '.md')
466
+ .filter(f => path.basename(f) !== 'README.md').length;
467
+ const countStr = `${agentCount} subagents`.padEnd(12);
468
+ console.log(` ${countStr} → ${path.join(baseDir, cfg.agentsSubdir)}`);
469
+ }
399
470
  return;
400
471
  }
401
472
 
@@ -531,6 +602,21 @@ async function install(runtime, scope, options = {}) {
531
602
  });
532
603
  }
533
604
 
605
+ // ── 2.2 Install Subagents (native Claude-Code agents, both scopes) ──────────
606
+ // The 154 imported subagents are Claude-Code-native .md files; Claude Code
607
+ // auto-discovers them from <runtime>/agents/. Installed for BOTH scopes so a
608
+ // global install also exposes them. Mirrored to .claude/agents/ for non-claude
609
+ // local runtimes (same cross-IDE rationale as the command mirror above).
610
+ if (cfg.agentsSubdir && !selfInstall) {
611
+ const agentsDir = path.join(baseDir, cfg.agentsSubdir);
612
+ const count = installSubagents(agentsDir, { noOverwrite: !force });
613
+ if (count > 0) Theme.printResolved(`${c.bold(`${count} subagents`)} (native agents)`);
614
+ }
615
+ if (scope === 'local' && runtime !== 'claude' && !selfInstall) {
616
+ const mirrorDir = path.join(process.cwd(), '.claude', 'agents');
617
+ installSubagents(mirrorDir, { noOverwrite: !force });
618
+ }
619
+
534
620
  // ── 3. Framework files (local scope only, non-self-install) ─────────────────
535
621
  if (scope === 'local' && !selfInstall) {
536
622
  // .mindforge/ — framework engine files
@@ -650,9 +736,19 @@ async function install(runtime, scope, options = {}) {
650
736
  }
651
737
  });
652
738
 
653
- // ✨ SOVEREIGN INITIALIZATION: Mark project as PQAS & Proactive enabled
739
+ // ✨ SOVEREIGN INITIALIZATION: report actual security posture honestly.
740
+ // The PQAS minter is gated SOLELY behind experimental.pqc_demo (see
741
+ // bin/governance/quantum-crypto.js: getProvider/_assertPqcDemoEnabled). When
742
+ // that flag is off (the default) PQAS is inert/simulated — claiming it is
743
+ // "enabled" would contradict the engine and mislead operators (UC-22).
654
744
  Theme.printStatus(c.magenta('Sovereign Intelligence v8.2.0 activated'), 'done');
655
- Theme.printStatus(c.dim(' - Post-Quantum Agentic Security (PQAS) enabled'), 'info');
745
+ if (isPqcDemoEnabled(process.cwd())) {
746
+ Theme.printStatus(c.dim(' - Post-Quantum Agentic Security (PQAS): SIMULATED demo ENABLED '
747
+ + '(experimental.pqc_demo=true — simulated lattice crypto, NOT production trust)'), 'info');
748
+ } else {
749
+ Theme.printStatus(c.dim(' - Post-Quantum Agentic Security (PQAS): available in simulated/experimental '
750
+ + 'mode (inactive by default — set experimental.pqc_demo=true to enable the simulated demo)'), 'info');
751
+ }
656
752
  Theme.printStatus(c.dim(' - Proactive Semantic Intent Harvesting active'), 'info');
657
753
 
658
754
  // bin/ utilities (remaining non-engine scripts)
@@ -683,9 +779,16 @@ async function uninstall(runtime, scope, options = {}) {
683
779
  const cmdsDir = norm(path.join(baseDir, cfg.commandsSubdir));
684
780
  const entryFile = norm(path.join(baseDir, cfg.entryFile));
685
781
 
782
+ const agentsDir = cfg.agentsSubdir ? norm(path.join(baseDir, cfg.agentsSubdir)) : null;
783
+ const importedAgents = importedSubagentBasenames();
784
+
686
785
  console.log(`\n Uninstalling MindForge (${runtime} / ${scope})...`);
687
786
  if (dryRun) {
688
787
  console.log(` Would remove: ${cmdsDir}`);
788
+ if (agentsDir && fsu.exists(agentsDir)) {
789
+ const present = fsu.listFiles(agentsDir).filter(f => importedAgents.has(f)).length;
790
+ if (present > 0) console.log(` Would remove: ${present} imported subagents from ${agentsDir}`);
791
+ }
689
792
  if (fsu.exists(entryFile) && fsu.read(entryFile).includes('MindForge'))
690
793
  console.log(` Would remove: ${entryFile}`);
691
794
  return;
@@ -697,6 +800,18 @@ async function uninstall(runtime, scope, options = {}) {
697
800
  console.log(` ✅ Removed: ${cmdsDir}`);
698
801
  }
699
802
 
803
+ // Remove ONLY our imported subagents — leave the user's own agents/ files intact.
804
+ if (agentsDir && fsu.exists(agentsDir)) {
805
+ let removed = 0;
806
+ for (const f of fsu.listFiles(agentsDir)) {
807
+ if (importedAgents.has(f)) {
808
+ fs.unlinkSync(path.join(agentsDir, f));
809
+ removed++;
810
+ }
811
+ }
812
+ if (removed > 0) console.log(` ✅ Removed: ${removed} imported subagents from ${agentsDir}`);
813
+ }
814
+
700
815
  // Remove entry file only if it's a MindForge-generated file
701
816
  if (fsu.exists(entryFile) && fsu.read(entryFile).includes('MindForge')) {
702
817
  fs.unlinkSync(entryFile);
@@ -715,6 +830,7 @@ function collectManifestStats() {
715
830
  const stats = {
716
831
  personas: 0,
717
832
  skills: 0,
833
+ subagents: 0,
718
834
  governance: 0,
719
835
  integrations: 0,
720
836
  actions: 0,
@@ -731,6 +847,13 @@ function collectManifestStats() {
731
847
  stats.integrations = fsu.listFiles(path.join(forgeSrc, 'integrations')).filter(f => f.endsWith('.md')).length;
732
848
  }
733
849
 
850
+ // Imported subagents (subagents/categories/**, excluding category READMEs)
851
+ const subagentsSrc = src('subagents', 'categories');
852
+ if (fsu.exists(subagentsSrc)) {
853
+ stats.subagents = fsu.listFilesRecursive(subagentsSrc, '.md')
854
+ .filter(f => path.basename(f) !== 'README.md').length;
855
+ }
856
+
734
857
  // Docs & Templates count
735
858
  const refSrc = src('docs', 'references');
736
859
  const tmpSrc = src('docs', 'templates');
@@ -748,7 +871,7 @@ function collectManifestStats() {
748
871
  }
749
872
  } catch (e) {
750
873
  // Fallback to default values if counting fails
751
- return { personas: 117, skills: 20, governance: 4, integrations: 6, actions: 71, docs: 12, templates: 21 };
874
+ return { personas: 117, skills: 20, subagents: 154, governance: 4, integrations: 6, actions: 71, docs: 12, templates: 21 };
752
875
  }
753
876
 
754
877
  return stats;
@@ -100,10 +100,48 @@ class EISClient {
100
100
  return [];
101
101
  }
102
102
 
103
- // TODO: implement when remote nodes are available
103
+ /**
104
+ * Verifies the provenance of a remote knowledge entry by cryptographically
105
+ * checking its signature against the signer DID's registered public key.
106
+ *
107
+ * HONEST / FAIL-CLOSED CONTRACT (UC-22, finding #22): this method NEVER
108
+ * returns true for a signature it has not actually verified. It returns true
109
+ * ONLY when ZTAI.verifySignature confirms the signature against a public key
110
+ * that is resolvable in the local trust registry. Every other case fails
111
+ * closed → false:
112
+ * - no/empty signature,
113
+ * - no signer DID on the entry,
114
+ * - the DID is not resolvable here (e.g. a genuinely remote peer whose key
115
+ * is not in the local registry — there is no remote DID-resolution infra
116
+ * yet, see resolveRemoteNode),
117
+ * - tampered payload or signature (crypto.verify returns false / throws).
118
+ *
119
+ * @param {{did?: string, signedData?: string}} entry - Provenance-bearing entry.
120
+ * `did` is the signer's DID; `signedData` is the exact canonical bytes that
121
+ * were signed (defaults to a deterministic JSON of the entry if absent).
122
+ * @param {string} signature - Base64 signature to verify.
123
+ * @returns {boolean} true only if cryptographically verified; false otherwise.
124
+ */
104
125
  verifyRemoteProvenance(entry, signature) {
105
- if (!signature) return false;
106
- return true;
126
+ if (!signature || typeof signature !== 'string') return false;
127
+ if (!entry || typeof entry !== 'object') return false;
128
+
129
+ const did = entry.did;
130
+ if (!did || typeof did !== 'string') return false;
131
+
132
+ // Canonical signed bytes: prefer an explicit signedData field, else a
133
+ // deterministic JSON of the entry (excluding the signature envelope).
134
+ const signedData = typeof entry.signedData === 'string'
135
+ ? entry.signedData
136
+ : JSON.stringify(entry);
137
+
138
+ try {
139
+ // ZTAI.verifySignature throws for an unresolvable (unregistered) DID —
140
+ // treat that as fail-closed rather than asserting verified provenance.
141
+ return ZTAI.verifySignature(did, signedData, signature) === true;
142
+ } catch {
143
+ return false;
144
+ }
107
145
  }
108
146
 
109
147
  // TODO: implement when remote nodes are available
@@ -113,7 +151,10 @@ class EISClient {
113
151
 
114
152
  /**
115
153
  * [HARDEN] Generates a cryptographically signed auth header using the agent's DID.
116
- * This ensures verifiable provenance of knowledge within the mesh.
154
+ * This attaches OUTBOUND provenance to locally-originated requests (it signs
155
+ * what this node sends). It does NOT verify the provenance of inbound remote
156
+ * entries — that is verifyRemoteProvenance's job, which fails closed unless a
157
+ * signature is cryptographically verified against a resolvable public key.
117
158
  */
118
159
  async getAuthHeader(action, resource) {
119
160
  const manager = new ZTAI();
@@ -56,6 +56,26 @@ class VectorHub {
56
56
  }
57
57
  }
58
58
 
59
+ /**
60
+ * Idempotently add a column to an existing table (lightweight migration).
61
+ * SQLite has no "ADD COLUMN IF NOT EXISTS", so we run the ALTER and swallow
62
+ * only the "duplicate column name" error — which simply means the column is
63
+ * already present (the table was created with it, or a prior run added it).
64
+ * Any other error is re-thrown so genuine schema problems surface loudly.
65
+ * @param {string} table
66
+ * @param {string} column
67
+ * @param {string} typeDecl - e.g. 'TEXT', 'INTEGER DEFAULT 0'
68
+ */
69
+ _addColumnIfMissing(table, column, typeDecl) {
70
+ try {
71
+ this._db.run(`ALTER TABLE ${table} ADD COLUMN ${column} ${typeDecl}`);
72
+ } catch (err) {
73
+ if (!/duplicate column name/i.test(err && err.message)) {
74
+ throw err;
75
+ }
76
+ }
77
+ }
78
+
59
79
  /**
60
80
  * Initialize the WASM SQLite database and create tables + indexes.
61
81
  */
@@ -124,11 +144,23 @@ class VectorHub {
124
144
  id TEXT PRIMARY KEY,
125
145
  request_id TEXT NOT NULL,
126
146
  status TEXT NOT NULL,
147
+ did TEXT,
148
+ signed_message TEXT,
127
149
  attestation_payload TEXT,
128
150
  timestamp TEXT NOT NULL
129
151
  )
130
152
  `);
131
153
 
154
+ // UC-22 (audit finding #2): orbital attestations must carry the signer DID
155
+ // and the EXACT canonical message that was signed so verify() can re-check
156
+ // the cryptographic signature instead of trusting status='APPROVED' alone.
157
+ // CREATE TABLE IF NOT EXISTS won't add columns to a database created before
158
+ // this fix, so back-fill them with guarded ALTER TABLE statements. SQLite
159
+ // throws "duplicate column name" when the column already exists — that case
160
+ // is the success path (already migrated), so it is swallowed.
161
+ this._addColumnIfMissing('attestations', 'did', 'TEXT');
162
+ this._addColumnIfMissing('attestations', 'signed_message', 'TEXT');
163
+
132
164
  this._db.run(`
133
165
  CREATE TABLE IF NOT EXISTS mesh_config (
134
166
  key TEXT PRIMARY KEY,
@@ -6,6 +6,10 @@
6
6
 
7
7
  const SEVERITY_ORDER = ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'];
8
8
 
9
+ // A severity spread of this many levels (or more) within a single location-group
10
+ // is treated as a contradiction (e.g. CRITICAL=3 vs LOW=0 → gap 3).
11
+ const CONTRADICTION_GAP_THRESHOLD = 2;
12
+
9
13
  function synthesizeFindings(reviews) {
10
14
  const allFindings = [];
11
15
  const modelSpecific = {};
@@ -18,8 +22,9 @@ function synthesizeFindings(reviews) {
18
22
  }
19
23
  }
20
24
 
21
- // Detect consensus
25
+ // Detect consensus and contradictions from the same location-groups.
22
26
  const consensus = [];
27
+ const contradictions = [];
23
28
  const processed = new Set();
24
29
 
25
30
  for (let i = 0; i < allFindings.length; i++) {
@@ -31,7 +36,7 @@ function synthesizeFindings(reviews) {
31
36
  for (let j = i + 1; j < allFindings.length; j++) {
32
37
  if (processed.has(j)) continue;
33
38
  const f2 = allFindings[j];
34
-
39
+
35
40
  if (isSameFinding(f1, f2)) {
36
41
  group.push(f2);
37
42
  processed.add(j);
@@ -45,13 +50,12 @@ function synthesizeFindings(reviews) {
45
50
  severity: getHighestSeverity(group.map(f => f.severity)),
46
51
  models: group.map(f => f.model),
47
52
  });
53
+
54
+ const contradiction = detectContradiction(f1.location, group);
55
+ if (contradiction) contradictions.push(contradiction);
48
56
  }
49
57
  }
50
58
 
51
- // Detect contradictions (large severity gap on same finding)
52
- const contradictions = [];
53
- // (In a real implementation, we'd more deeply analyze conflicting logic)
54
-
55
59
  return {
56
60
  consensus,
57
61
  model_specific: modelSpecific,
@@ -92,6 +96,31 @@ function normalizeLocation(loc) {
92
96
  });
93
97
  }
94
98
 
99
+ function severityRank(severity) {
100
+ const idx = SEVERITY_ORDER.indexOf(severity);
101
+ return idx < 0 ? 0 : idx;
102
+ }
103
+
104
+ // A location-group is contradictory when its reviews disagree on severity by
105
+ // CONTRADICTION_GAP_THRESHOLD levels or more (e.g. CRITICAL vs LOW). Reuses the
106
+ // already-computed location-group rather than re-deriving it.
107
+ function detectContradiction(location, group) {
108
+ const ranks = group.map(f => severityRank(f.severity));
109
+ const maxRank = Math.max(...ranks);
110
+ const minRank = Math.min(...ranks);
111
+
112
+ if (maxRank - minRank < CONTRADICTION_GAP_THRESHOLD) return null;
113
+
114
+ return {
115
+ location,
116
+ severities: group.map(f => f.severity),
117
+ models: group.map(f => f.model),
118
+ description: `Severity disagreement at ${location}: ` +
119
+ `${SEVERITY_ORDER[minRank]} vs ${SEVERITY_ORDER[maxRank]} ` +
120
+ `(${maxRank - minRank}-level gap across ${group.length} reviews)`,
121
+ };
122
+ }
123
+
95
124
  function getHighestSeverity(severities) {
96
125
  let highest = 0;
97
126
  for (const s of severities) {
@@ -70,15 +70,41 @@ function tagUntrusted(content, meta) {
70
70
  // character in a regex literal (eslint no-control-regex).
71
71
  const NUL = String.fromCharCode(0);
72
72
 
73
+ // ${IFS} / $IFS are shell-internal field separators that expand to whitespace.
74
+ // Attackers use them to avoid literal spaces between a destructive token and
75
+ // its flags (rm${IFS}-rf${IFS}/). We normalize them back to a space so the
76
+ // existing rm/-rf patterns catch the de-obfuscated form (audit #8).
77
+ const IFS_TOKEN = /\$\{IFS\}|\$IFS/g;
78
+
79
+ /**
80
+ * De-obfuscates shell metacharacter tricks WITHOUT emulating a real shell.
81
+ * Strips quotes (' ") and backslash escapes, collapses ${IFS}/$IFS to a space,
82
+ * then collapses runs of whitespace. This turns r''m, r"m, r\m and
83
+ * rm${IFS}-rf${IFS}/ back into plain `rm -rf /` so the existing patterns fire.
84
+ * Intentionally conservative: it removes characters rather than interpreting
85
+ * them, which can only make a string MORE likely to match (fail-toward-block).
86
+ */
87
+ function normalizeShell(input) {
88
+ return input
89
+ .split(NUL).join('') // shells ignore NUL; never let it split a token
90
+ .replace(IFS_TOKEN, ' ') // ${IFS}/$IFS -> space
91
+ .replace(/[\\'"]/g, '') // drop backslash escapes and quote chars
92
+ .replace(/\s+/g, ' '); // collapse whitespace runs
93
+ }
94
+
73
95
  /**
74
96
  * Detects high-impact / destructive commands via case-insensitive pattern matching.
75
97
  * Returns true if the command matches known destructive patterns.
98
+ *
99
+ * The detector errs toward blocking by design: this feeds a PreToolUse gate
100
+ * where approval friction is strictly preferable to a destructive bypass.
76
101
  */
77
102
  function isHighImpact(command) {
78
- // Strip null bytes first shells ignore them, so an attacker must not be
79
- // able to use a NUL to split a destructive token and slip past the patterns.
80
- const sanitized = String(command).split(NUL).join('');
103
+ // Normalize first so metacharacter-obfuscated commands (audit #8) are matched
104
+ // by the SAME pattern set as their plain-text equivalents.
105
+ const sanitized = normalizeShell(String(command));
81
106
  const patterns = [
107
+ // ── Original allowlist (unchanged) ──────────────────────────────────────
82
108
  /rm\s+(-\w*r\w*\s+-\w*f|(-\w*f\w*\s+-\w*r)|-\w*rf|-\w*fr)/i,
83
109
  /git\s+push\s+.*--force/i,
84
110
  /git\s+push\s+.*-f/i,
@@ -87,9 +113,75 @@ function isHighImpact(command) {
87
113
  /delete\s+from/i,
88
114
  /truncate\s+table/i,
89
115
  /\bmkfs(\.\w+)?\s+\/dev\//i,
90
- /\bdd\b.*\bof=\/dev\//i,
116
+ // #11: any dd write target, not just /dev/ (dd if=... of=important.db).
117
+ // Original /dev/-only check is a subset of this, so it stays covered.
118
+ /\bdd\b.*\bof=/i,
91
119
  /\b(curl|wget)\b.*\|\s*(bash|sh|zsh)\b/i,
92
120
  /^\s*find\s+.*-delete\b/i,
121
+
122
+ // ── #4 Command/process substitution RCE ─────────────────────────────────
123
+ // `eval` anywhere is high-impact (dynamic code execution).
124
+ /\beval\b/i,
125
+ // Command substitution $(...) or backtick combined with a network fetch.
126
+ /\$\(\s*(curl|wget)\b/i,
127
+ new RegExp(String.fromCharCode(96) + '\\s*(curl|wget)\\b', 'i'),
128
+ // Process substitution feeding an interpreter: bash <(...), sh <(...).
129
+ /\b(bash|sh|zsh)\b.*<\(/i,
130
+ // Curl/wget directly inside a process substitution.
131
+ /<\(\s*(curl|wget)\b/i,
132
+
133
+ // ── #5 Interpreter invocation of a script file ──────────────────────────
134
+ // source <file> and `. <file>` are unambiguous script execution.
135
+ /\bsource\s+\S+/i,
136
+ /(^|[;&|]|\s)\.\s+\S+\.\w+/i,
137
+ // bash/sh/zsh running a .sh script — clearly script execution. Kept broad
138
+ // (any .sh) because shelling out to an arbitrary shell script is itself a
139
+ // strong execution signal; this also covers untrusted paths like
140
+ // `bash /tmp/x.sh`.
141
+ /\b(bash|sh|zsh)\s+\S*\.sh\b/i,
142
+ // node/python/etc. running a script — narrowed (UC-22). Running a project
143
+ // file is THE default safe action in a Node/Python repo, so a blanket
144
+ // `node <file>.js` match is a terrible signal-to-noise ratio and blocked
145
+ // the project's OWN idiom (`node tests/run-all.js`). We now flag ONLY when
146
+ // the script path looks UNTRUSTED — an absolute path (/...), a writable
147
+ // temp dir (/tmp, /var/tmp, /dev/shm), or a home-relative path (~/...) —
148
+ // i.e. the write-then-execute attack chain (drop payload in a writable
149
+ // location, then run it). Project-relative paths (tests/run-all.js,
150
+ // bin/foo.js, scripts/build.py, index.js) are NOT matched. Piped/
151
+ // substituted execution (curl | bash, $(...), <(...), backticks) is
152
+ // already covered by the #4 patterns above.
153
+ /\b(node|python3?|ruby|perl)\s+(~\/|\/)\S*\.(js|mjs|cjs|ts|py|rb|pl)\b/i,
154
+
155
+ // ── #7 Redirect-overwrite of critical files / devices ───────────────────
156
+ // > or >> targeting an absolute sensitive path (/etc/, /dev/, /boot/, /sys/,
157
+ // /proc/, /var/, /usr/, /bin/, /sbin/, /lib/). Project-local redirects
158
+ // (> out.log) are NOT matched.
159
+ />>?\s*\/(etc|dev|boot|sys|proc|var|usr|bin|sbin|lib|root|lib64)\//i,
160
+
161
+ // ── #8 handled by normalizeShell() above (de-obfuscation), no pattern here.
162
+
163
+ // ── #9 chmod dangerous modes ────────────────────────────────────────────
164
+ // Canonical dangerous octal modes only: 000 (full lockout) and the
165
+ // world-writable 666/777 family. Common safe modes (644/755/600/700/640)
166
+ // and symbolic modes (chmod +x build.sh) are intentionally NOT matched.
167
+ // ANY recursive chmod is also treated as high-impact regardless of mode.
168
+ /\bchmod\b.*\b(000|0?666|0?777)\b/i,
169
+ /\bchmod\s+-\w*[rR]/i,
170
+
171
+ // ── #10 chown recursive ─────────────────────────────────────────────────
172
+ /\bchown\s+-\w*[rR]/i,
173
+
174
+ // ── #12 mv of root / into /dev/null ─────────────────────────────────────
175
+ /\bmv\b.*\/dev\/null\b/i,
176
+ /\bmv\s+(-\w+\s+)?\/\s/i,
177
+
178
+ // ── #13 Process killers ─────────────────────────────────────────────────
179
+ /\bkill\s+-9\s+-1\b/i,
180
+ /\bkillall\b/i,
181
+ /\bpkill\b/i,
182
+
183
+ // ── #14 Power-state commands ────────────────────────────────────────────
184
+ /\b(shutdown|reboot|halt|poweroff)\b/i,
93
185
  ];
94
186
  return patterns.some(pattern => pattern.test(sanitized));
95
187
  }