contextos-agents 1.7.0 → 2.0.0-beta.3

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 (313) hide show
  1. package/.agents/AGENTS.md +16 -50
  2. package/.agents/adapters/aider/export.js +117 -99
  3. package/.agents/adapters/claude/export.js +68 -26
  4. package/.agents/adapters/copilot/export.js +90 -53
  5. package/.agents/adapters/cursor/export.js +100 -104
  6. package/.agents/adapters/drift-detector.js +196 -0
  7. package/.agents/adapters/gemini/export.js +76 -45
  8. package/.agents/adapters/pure-compiler.js +443 -0
  9. package/.agents/adapters/zed/export.js +104 -96
  10. package/.agents/compiled/registry.v2.json +504 -0
  11. package/.agents/compiled/registry.v2.sha256 +1 -0
  12. package/.agents/compiler/manifest-compiler.js +963 -0
  13. package/.agents/compiler/vendor/yaml.LICENSE.txt +13 -0
  14. package/.agents/compiler/vendor/yaml.SBOM.json +6 -0
  15. package/.agents/compiler/vendor/yaml.js +139 -0
  16. package/.agents/core/profiles/init.yaml +25 -0
  17. package/.agents/core/skills/context-manager/skill.yaml +3 -5
  18. package/.agents/core/skills/context-os/SKILL.md +3 -6
  19. package/.agents/core/skills/context-os/skill.yaml +3 -8
  20. package/.agents/core/skills/engineering-workflow/skill.yaml +1 -7
  21. package/.agents/core/skills/gemini-precision/skill.yaml +1 -6
  22. package/.agents/core/skills/gstack-roles/skill.yaml +3 -6
  23. package/.agents/core/skills/ponytail-mindset/skill.yaml +1 -7
  24. package/.agents/core/skills/security/skill.yaml +15 -3
  25. package/.agents/ctx.js +537 -112
  26. package/.agents/customization-dx.js +282 -0
  27. package/.agents/doctor.js +855 -66
  28. package/.agents/filesystem/index.js +71 -0
  29. package/.agents/filesystem/journaled-transaction.js +451 -0
  30. package/.agents/filesystem/lockfile-v2.js +275 -0
  31. package/.agents/filesystem/platform-hardening.js +222 -0
  32. package/.agents/filesystem/project-lock.js +218 -0
  33. package/.agents/filesystem/safe-path.js +256 -0
  34. package/.agents/generated/claude/skills/context-os/SKILL.md +1 -1
  35. package/.agents/generated/gemini/skills/context-os/SKILL.md +2 -2
  36. package/.agents/plugins/contextos/plugin.json +1 -1
  37. package/.agents/plugins.js +278 -64
  38. package/.agents/profiles.js +486 -51
  39. package/.agents/resolver/canonical-resolver.js +1348 -0
  40. package/.agents/resolver.js +50 -534
  41. package/.agents/rules/rule-catalog.js +525 -0
  42. package/.agents/schemas/attestation.review.v1.json +111 -0
  43. package/.agents/schemas/attestation.verification.v1.json +85 -0
  44. package/.agents/schemas/lockfile.v2.schema.json +134 -0
  45. package/.agents/schemas/profile.v2.schema.json +114 -0
  46. package/.agents/schemas/runtime.thread.v1.json +192 -0
  47. package/.agents/schemas/skill.manifest.v2.json +177 -0
  48. package/.agents/schemas/verification.spec.v1.json +39 -0
  49. package/.agents/schemas/workspace.graph.schema.json +106 -0
  50. package/.agents/skills-index.json +6 -166
  51. package/.agents/stats.js +9 -9
  52. package/.agents/transaction-core/event-store.js +288 -0
  53. package/.agents/transaction-core/idempotency.js +129 -0
  54. package/.agents/transaction-core/ipc-lock.js +311 -0
  55. package/.agents/transaction-core/plugin-supply-chain-bundle.js +436 -0
  56. package/.agents/validate.js +44 -1
  57. package/.agents/watch.js +354 -102
  58. package/.agents/workspace/workspace-graph.js +778 -0
  59. package/README.md +59 -387
  60. package/benchmarks/v2/analysis/statistics.js +140 -0
  61. package/benchmarks/v2/analysis/stats.js +69 -0
  62. package/benchmarks/v2/arms/arm-definitions.js +79 -0
  63. package/benchmarks/v2/dataset.schema.json +34 -0
  64. package/benchmarks/v2/evaluators/index.js +25 -0
  65. package/benchmarks/v2/evaluators/verified-success.js +116 -0
  66. package/benchmarks/v2/harness/runner.js +88 -0
  67. package/benchmarks/v2/pilot-tasks.json +392 -0
  68. package/bin/commands/recover.js +88 -0
  69. package/bin/commands/update.js +80 -17
  70. package/bin/commands.js +62 -25
  71. package/bin/index.js +138 -81
  72. package/bin/lib/lockfile.js +5 -3
  73. package/bin/lib/safe-writer.js +34 -3
  74. package/package.json +87 -72
  75. package/registry.json +2 -2
  76. package/registry.v2.schema.json +86 -0
  77. package/.agents/core/profiles/backend.yaml +0 -47
  78. package/.agents/core/profiles/enterprise.yaml +0 -46
  79. package/.agents/core/profiles/frontend.yaml +0 -46
  80. package/.agents/core/profiles/hackathon.yaml +0 -45
  81. package/.agents/core/profiles/mvp.yaml +0 -44
  82. package/.agents/core/profiles/startup.yaml +0 -48
  83. package/.agents/core/skills/adapters/EXAMPLES.md +0 -19
  84. package/.agents/core/skills/adapters/SKILL.md +0 -105
  85. package/.agents/core/skills/adapters/TROUBLESHOOTING.md +0 -7
  86. package/.agents/core/skills/adapters/VALIDATION.json +0 -12
  87. package/.agents/core/skills/adapters/skill.yaml +0 -16
  88. package/.agents/core/skills/architecture-diagrams/SKILL.md +0 -108
  89. package/.agents/core/skills/architecture-diagrams/VALIDATION.json +0 -12
  90. package/.agents/core/skills/architecture-diagrams/skill.yaml +0 -12
  91. package/.agents/core/skills/brutalist-design/SKILL.md +0 -150
  92. package/.agents/core/skills/brutalist-design/VALIDATION.json +0 -12
  93. package/.agents/core/skills/brutalist-design/skill.yaml +0 -12
  94. package/.agents/core/skills/database/EXAMPLES.md +0 -74
  95. package/.agents/core/skills/database/SKILL.md +0 -101
  96. package/.agents/core/skills/database/TROUBLESHOOTING.md +0 -18
  97. package/.agents/core/skills/database/VALIDATION.json +0 -11
  98. package/.agents/core/skills/database/skill.yaml +0 -31
  99. package/.agents/core/skills/ddd/EXAMPLES.md +0 -42
  100. package/.agents/core/skills/ddd/SKILL.md +0 -247
  101. package/.agents/core/skills/ddd/TROUBLESHOOTING.md +0 -19
  102. package/.agents/core/skills/ddd/VALIDATION.json +0 -12
  103. package/.agents/core/skills/ddd/ddd.md +0 -178
  104. package/.agents/core/skills/ddd/skill.yaml +0 -17
  105. package/.agents/core/skills/decisions/EXAMPLES.md +0 -35
  106. package/.agents/core/skills/decisions/SKILL.md +0 -90
  107. package/.agents/core/skills/decisions/TROUBLESHOOTING.md +0 -13
  108. package/.agents/core/skills/decisions/VALIDATION.json +0 -12
  109. package/.agents/core/skills/decisions/skill.yaml +0 -16
  110. package/.agents/core/skills/docker/EXAMPLES.md +0 -56
  111. package/.agents/core/skills/docker/SKILL.md +0 -63
  112. package/.agents/core/skills/docker/TROUBLESHOOTING.md +0 -18
  113. package/.agents/core/skills/docker/VALIDATION.json +0 -11
  114. package/.agents/core/skills/docker/skill.yaml +0 -29
  115. package/.agents/core/skills/fastapi/EXAMPLES.md +0 -36
  116. package/.agents/core/skills/fastapi/SKILL.md +0 -148
  117. package/.agents/core/skills/fastapi/TROUBLESHOOTING.md +0 -19
  118. package/.agents/core/skills/fastapi/VALIDATION.json +0 -12
  119. package/.agents/core/skills/fastapi/fastapi.md +0 -112
  120. package/.agents/core/skills/fastapi/skill.yaml +0 -17
  121. package/.agents/core/skills/generators/EXAMPLES.md +0 -19
  122. package/.agents/core/skills/generators/SKILL.md +0 -112
  123. package/.agents/core/skills/generators/TROUBLESHOOTING.md +0 -7
  124. package/.agents/core/skills/generators/VALIDATION.json +0 -12
  125. package/.agents/core/skills/generators/skill.yaml +0 -25
  126. package/.agents/core/skills/generators/templates/API.md +0 -77
  127. package/.agents/core/skills/generators/templates/ARCHITECTURE.md +0 -70
  128. package/.agents/core/skills/generators/templates/DATABASE.md +0 -42
  129. package/.agents/core/skills/generators/templates/DECISION.md +0 -46
  130. package/.agents/core/skills/generators/templates/PRD.md +0 -67
  131. package/.agents/core/skills/generators/templates/PROJECT_GRAPH.md +0 -56
  132. package/.agents/core/skills/generators/templates/ROADMAP.md +0 -51
  133. package/.agents/core/skills/generators/templates/TASKS.md +0 -43
  134. package/.agents/core/skills/generators/templates/UI.md +0 -73
  135. package/.agents/core/skills/graphify/EXAMPLES.md +0 -73
  136. package/.agents/core/skills/graphify/SKILL.md +0 -130
  137. package/.agents/core/skills/graphify/VALIDATION.json +0 -12
  138. package/.agents/core/skills/graphify/skill.yaml +0 -18
  139. package/.agents/core/skills/impeccable-design/EXAMPLES.md +0 -26
  140. package/.agents/core/skills/impeccable-design/SKILL.md +0 -201
  141. package/.agents/core/skills/impeccable-design/TROUBLESHOOTING.md +0 -19
  142. package/.agents/core/skills/impeccable-design/VALIDATION.json +0 -12
  143. package/.agents/core/skills/impeccable-design/skill.yaml +0 -20
  144. package/.agents/core/skills/interview-me/SKILL.md +0 -97
  145. package/.agents/core/skills/interview-me/VALIDATION.json +0 -12
  146. package/.agents/core/skills/interview-me/skill.yaml +0 -12
  147. package/.agents/core/skills/microservices/EXAMPLES.md +0 -38
  148. package/.agents/core/skills/microservices/SKILL.md +0 -164
  149. package/.agents/core/skills/microservices/TROUBLESHOOTING.md +0 -19
  150. package/.agents/core/skills/microservices/VALIDATION.json +0 -12
  151. package/.agents/core/skills/microservices/microservices.md +0 -119
  152. package/.agents/core/skills/microservices/skill.yaml +0 -17
  153. package/.agents/core/skills/minimalist-design/SKILL.md +0 -113
  154. package/.agents/core/skills/minimalist-design/VALIDATION.json +0 -12
  155. package/.agents/core/skills/minimalist-design/skill.yaml +0 -12
  156. package/.agents/core/skills/nestjs/EXAMPLES.md +0 -40
  157. package/.agents/core/skills/nestjs/SKILL.md +0 -139
  158. package/.agents/core/skills/nestjs/TROUBLESHOOTING.md +0 -19
  159. package/.agents/core/skills/nestjs/VALIDATION.json +0 -12
  160. package/.agents/core/skills/nestjs/nestjs.md +0 -103
  161. package/.agents/core/skills/nestjs/skill.yaml +0 -17
  162. package/.agents/core/skills/nextjs/EXAMPLES.md +0 -40
  163. package/.agents/core/skills/nextjs/SKILL.md +0 -163
  164. package/.agents/core/skills/nextjs/TROUBLESHOOTING.md +0 -19
  165. package/.agents/core/skills/nextjs/VALIDATION.json +0 -12
  166. package/.agents/core/skills/nextjs/nextjs.md +0 -67
  167. package/.agents/core/skills/nextjs/skill.yaml +0 -17
  168. package/.agents/core/skills/node/EXAMPLES.md +0 -80
  169. package/.agents/core/skills/node/SKILL.md +0 -128
  170. package/.agents/core/skills/node/TROUBLESHOOTING.md +0 -19
  171. package/.agents/core/skills/node/VALIDATION.json +0 -12
  172. package/.agents/core/skills/node/node.md +0 -87
  173. package/.agents/core/skills/node/skill.yaml +0 -17
  174. package/.agents/core/skills/performance/EXAMPLES.md +0 -30
  175. package/.agents/core/skills/performance/SKILL.md +0 -75
  176. package/.agents/core/skills/performance/TROUBLESHOOTING.md +0 -19
  177. package/.agents/core/skills/performance/VALIDATION.json +0 -12
  178. package/.agents/core/skills/performance/performance.md +0 -52
  179. package/.agents/core/skills/performance/skill.yaml +0 -17
  180. package/.agents/core/skills/react/EXAMPLES.md +0 -79
  181. package/.agents/core/skills/react/SKILL.md +0 -132
  182. package/.agents/core/skills/react/TROUBLESHOOTING.md +0 -19
  183. package/.agents/core/skills/react/VALIDATION.json +0 -12
  184. package/.agents/core/skills/react/react.md +0 -93
  185. package/.agents/core/skills/react/skill.yaml +0 -17
  186. package/.agents/core/skills/react-best-practices/SKILL.md +0 -155
  187. package/.agents/core/skills/react-best-practices/VALIDATION.json +0 -12
  188. package/.agents/core/skills/react-best-practices/skill.yaml +0 -14
  189. package/.agents/core/skills/redesign-audit/SKILL.md +0 -117
  190. package/.agents/core/skills/redesign-audit/VALIDATION.json +0 -12
  191. package/.agents/core/skills/redesign-audit/skill.yaml +0 -12
  192. package/.agents/core/skills/soft-design/SKILL.md +0 -108
  193. package/.agents/core/skills/soft-design/VALIDATION.json +0 -12
  194. package/.agents/core/skills/soft-design/skill.yaml +0 -12
  195. package/.agents/core/skills/state-management/EXAMPLES.md +0 -56
  196. package/.agents/core/skills/state-management/SKILL.md +0 -48
  197. package/.agents/core/skills/state-management/TROUBLESHOOTING.md +0 -18
  198. package/.agents/core/skills/state-management/VALIDATION.json +0 -11
  199. package/.agents/core/skills/state-management/skill.yaml +0 -28
  200. package/.agents/core/skills/subagent-orchestrator/SKILL.md +0 -117
  201. package/.agents/core/skills/subagent-orchestrator/VALIDATION.json +0 -12
  202. package/.agents/core/skills/subagent-orchestrator/skill.yaml +0 -12
  203. package/.agents/core/skills/system-design/EXAMPLES.md +0 -75
  204. package/.agents/core/skills/system-design/SKILL.md +0 -419
  205. package/.agents/core/skills/system-design/TROUBLESHOOTING.md +0 -19
  206. package/.agents/core/skills/system-design/VALIDATION.json +0 -12
  207. package/.agents/core/skills/system-design/skill.yaml +0 -20
  208. package/.agents/core/skills/system-design/system-design.md +0 -112
  209. package/.agents/core/skills/testing/EXAMPLES.md +0 -71
  210. package/.agents/core/skills/testing/SKILL.md +0 -70
  211. package/.agents/core/skills/testing/TROUBLESHOOTING.md +0 -18
  212. package/.agents/core/skills/testing/VALIDATION.json +0 -11
  213. package/.agents/core/skills/testing/skill.yaml +0 -32
  214. package/.agents/core/skills/typescript/EXAMPLES.md +0 -64
  215. package/.agents/core/skills/typescript/SKILL.md +0 -112
  216. package/.agents/core/skills/typescript/TROUBLESHOOTING.md +0 -19
  217. package/.agents/core/skills/typescript/VALIDATION.json +0 -12
  218. package/.agents/core/skills/typescript/skill.yaml +0 -17
  219. package/.agents/core/skills/typescript/typescript.md +0 -71
  220. package/.agents/core/skills/ui-design/EXAMPLES.md +0 -21
  221. package/.agents/core/skills/ui-design/SKILL.md +0 -124
  222. package/.agents/core/skills/ui-design/TROUBLESHOOTING.md +0 -19
  223. package/.agents/core/skills/ui-design/VALIDATION.json +0 -12
  224. package/.agents/core/skills/ui-design/skill.yaml +0 -17
  225. package/.agents/core/skills/ui-design/ui.md +0 -88
  226. package/.agents/core/skills/ui-ux-pro/EXAMPLES.md +0 -62
  227. package/.agents/core/skills/ui-ux-pro/SKILL.md +0 -375
  228. package/.agents/core/skills/ui-ux-pro/TROUBLESHOOTING.md +0 -19
  229. package/.agents/core/skills/ui-ux-pro/VALIDATION.json +0 -12
  230. package/.agents/core/skills/ui-ux-pro/skill.yaml +0 -19
  231. package/.agents/core/skills/ux-design/EXAMPLES.md +0 -36
  232. package/.agents/core/skills/ux-design/SKILL.md +0 -116
  233. package/.agents/core/skills/ux-design/TROUBLESHOOTING.md +0 -19
  234. package/.agents/core/skills/ux-design/VALIDATION.json +0 -12
  235. package/.agents/core/skills/ux-design/skill.yaml +0 -17
  236. package/.agents/core/skills/ux-design/ux.md +0 -80
  237. package/.agents/core/skills/vercel-optimize/SKILL.md +0 -83
  238. package/.agents/core/skills/vercel-optimize/VALIDATION.json +0 -12
  239. package/.agents/core/skills/vercel-optimize/scripts/collect-signals.mjs +0 -131
  240. package/.agents/core/skills/vercel-optimize/scripts/gate-investigations.mjs +0 -142
  241. package/.agents/core/skills/vercel-optimize/scripts/merge-signals.mjs +0 -143
  242. package/.agents/core/skills/vercel-optimize/scripts/scan-codebase.mjs +0 -174
  243. package/.agents/core/skills/vercel-optimize/skill.yaml +0 -18
  244. package/.agents/core/skills/web-accessibility/EXAMPLES.md +0 -39
  245. package/.agents/core/skills/web-accessibility/SKILL.md +0 -170
  246. package/.agents/core/skills/web-accessibility/TROUBLESHOOTING.md +0 -19
  247. package/.agents/core/skills/web-accessibility/VALIDATION.json +0 -12
  248. package/.agents/core/skills/web-accessibility/accessibility.md +0 -63
  249. package/.agents/core/skills/web-accessibility/skill.yaml +0 -17
  250. package/.agents/generated/claude/skills/adapters/SKILL.md +0 -126
  251. package/.agents/generated/claude/skills/architecture-diagrams/SKILL.md +0 -101
  252. package/.agents/generated/claude/skills/brutalist-design/SKILL.md +0 -145
  253. package/.agents/generated/claude/skills/database/SKILL.md +0 -191
  254. package/.agents/generated/claude/skills/ddd/SKILL.md +0 -305
  255. package/.agents/generated/claude/skills/decisions/SKILL.md +0 -134
  256. package/.agents/generated/claude/skills/docker/SKILL.md +0 -135
  257. package/.agents/generated/claude/skills/fastapi/SKILL.md +0 -200
  258. package/.agents/generated/claude/skills/generators/SKILL.md +0 -133
  259. package/.agents/generated/claude/skills/graphify/SKILL.md +0 -198
  260. package/.agents/generated/claude/skills/impeccable-design/SKILL.md +0 -241
  261. package/.agents/generated/claude/skills/interview-me/SKILL.md +0 -90
  262. package/.agents/generated/claude/skills/microservices/SKILL.md +0 -218
  263. package/.agents/generated/claude/skills/minimalist-design/SKILL.md +0 -108
  264. package/.agents/generated/claude/skills/nestjs/SKILL.md +0 -195
  265. package/.agents/generated/claude/skills/nextjs/SKILL.md +0 -219
  266. package/.agents/generated/claude/skills/node/SKILL.md +0 -224
  267. package/.agents/generated/claude/skills/performance/SKILL.md +0 -121
  268. package/.agents/generated/claude/skills/react/SKILL.md +0 -227
  269. package/.agents/generated/claude/skills/react-best-practices/SKILL.md +0 -146
  270. package/.agents/generated/claude/skills/redesign-audit/SKILL.md +0 -112
  271. package/.agents/generated/claude/skills/soft-design/SKILL.md +0 -103
  272. package/.agents/generated/claude/skills/state-management/SKILL.md +0 -120
  273. package/.agents/generated/claude/skills/subagent-orchestrator/SKILL.md +0 -110
  274. package/.agents/generated/claude/skills/system-design/SKILL.md +0 -507
  275. package/.agents/generated/claude/skills/testing/SKILL.md +0 -157
  276. package/.agents/generated/claude/skills/typescript/SKILL.md +0 -192
  277. package/.agents/generated/claude/skills/ui-design/SKILL.md +0 -161
  278. package/.agents/generated/claude/skills/ui-ux-pro/SKILL.md +0 -451
  279. package/.agents/generated/claude/skills/ux-design/SKILL.md +0 -168
  280. package/.agents/generated/claude/skills/vercel-optimize/SKILL.md +0 -76
  281. package/.agents/generated/claude/skills/web-accessibility/SKILL.md +0 -225
  282. package/.agents/generated/gemini/skills/adapters/SKILL.md +0 -135
  283. package/.agents/generated/gemini/skills/architecture-diagrams/SKILL.md +0 -107
  284. package/.agents/generated/gemini/skills/brutalist-design/SKILL.md +0 -151
  285. package/.agents/generated/gemini/skills/database/SKILL.md +0 -200
  286. package/.agents/generated/gemini/skills/ddd/SKILL.md +0 -314
  287. package/.agents/generated/gemini/skills/decisions/SKILL.md +0 -143
  288. package/.agents/generated/gemini/skills/docker/SKILL.md +0 -144
  289. package/.agents/generated/gemini/skills/fastapi/SKILL.md +0 -209
  290. package/.agents/generated/gemini/skills/generators/SKILL.md +0 -142
  291. package/.agents/generated/gemini/skills/graphify/SKILL.md +0 -205
  292. package/.agents/generated/gemini/skills/impeccable-design/SKILL.md +0 -250
  293. package/.agents/generated/gemini/skills/interview-me/SKILL.md +0 -96
  294. package/.agents/generated/gemini/skills/microservices/SKILL.md +0 -227
  295. package/.agents/generated/gemini/skills/minimalist-design/SKILL.md +0 -114
  296. package/.agents/generated/gemini/skills/nestjs/SKILL.md +0 -204
  297. package/.agents/generated/gemini/skills/nextjs/SKILL.md +0 -298
  298. package/.agents/generated/gemini/skills/node/SKILL.md +0 -323
  299. package/.agents/generated/gemini/skills/performance/SKILL.md +0 -185
  300. package/.agents/generated/gemini/skills/react/SKILL.md +0 -332
  301. package/.agents/generated/gemini/skills/react-best-practices/SKILL.md +0 -152
  302. package/.agents/generated/gemini/skills/redesign-audit/SKILL.md +0 -118
  303. package/.agents/generated/gemini/skills/soft-design/SKILL.md +0 -109
  304. package/.agents/generated/gemini/skills/state-management/SKILL.md +0 -129
  305. package/.agents/generated/gemini/skills/subagent-orchestrator/SKILL.md +0 -116
  306. package/.agents/generated/gemini/skills/system-design/SKILL.md +0 -631
  307. package/.agents/generated/gemini/skills/testing/SKILL.md +0 -166
  308. package/.agents/generated/gemini/skills/typescript/SKILL.md +0 -275
  309. package/.agents/generated/gemini/skills/ui-design/SKILL.md +0 -170
  310. package/.agents/generated/gemini/skills/ui-ux-pro/SKILL.md +0 -460
  311. package/.agents/generated/gemini/skills/ux-design/SKILL.md +0 -177
  312. package/.agents/generated/gemini/skills/vercel-optimize/SKILL.md +0 -82
  313. package/.agents/generated/gemini/skills/web-accessibility/SKILL.md +0 -300
@@ -12,21 +12,28 @@
12
12
 
13
13
  const fs = require('fs');
14
14
  const path = require('path');
15
+ const { ProjectMutationLock, JournaledTransaction } = require('./filesystem/index.js');
15
16
 
16
17
  const AGENTS_DIR = path.join(__dirname);
17
18
  const PROFILES_DIR = path.join(AGENTS_DIR, 'core', 'profiles');
18
19
 
19
20
  /**
20
21
  * @typedef {Object} ProfileConfig
22
+ * @property {number} [schemaVersion] - Profile schema version
21
23
  * @property {string} id - Profile unique slug
22
24
  * @property {string} name - Human-readable profile title
23
25
  * @property {string} description - Summary of the profile's purpose
24
- * @property {string[]} prefer_skills - Priority skills favored by this profile
25
- * @property {string[]} exclude_skills - Skills filtered out for this profile
26
- * @property {string[]} generate_docs - Documentation templates to generate
27
- * @property {string[]} skip_docs - Documentation to skip
28
- * @property {Object} enforce - Invariant rules to enforce
29
- * @property {Object} defaults - Default parameters
26
+ * @property {Object} skills - Profile skill groups (required, preferred, excluded)
27
+ * @property {Object} policy - Quality gate policies
28
+ * @property {Object} generation - Document templates to generate
29
+ * @property {Object} technologyDefaults - Default framework/tool selections
30
+ * @property {string[]} prefer_skills - Legacy priority skills
31
+ * @property {string[]} exclude_skills - Legacy filtered out skills
32
+ * @property {string[]} require_skills - Legacy mandatory skills
33
+ * @property {string[]} generate_docs - Legacy documentation templates
34
+ * @property {string[]} skip_docs - Legacy documentation to skip
35
+ * @property {Object} enforce - Legacy invariant rules
36
+ * @property {Object} defaults - Legacy default parameters
30
37
  */
31
38
 
32
39
  /**
@@ -37,17 +44,39 @@ const PROFILES_DIR = path.join(AGENTS_DIR, 'core', 'profiles');
37
44
  */
38
45
  function parseYamlProfile(text) {
39
46
  const result = {
47
+ schemaVersion: 1,
40
48
  id: '',
41
49
  name: '',
42
50
  description: '',
51
+ skills: {
52
+ required: [],
53
+ preferred: [],
54
+ excluded: [],
55
+ },
56
+ policy: {
57
+ verification: 'recommended',
58
+ review: 'recommended',
59
+ security: 'recommended',
60
+ adr: 'off',
61
+ testing: 'recommended',
62
+ accessibility: 'off',
63
+ },
64
+ generation: {
65
+ documents: [],
66
+ },
67
+ technologyDefaults: {},
43
68
  prefer_skills: [],
44
69
  exclude_skills: [],
70
+ require_skills: [],
45
71
  generate_docs: [],
46
72
  skip_docs: [],
47
73
  enforce: {},
48
74
  defaults: {},
49
75
  };
50
76
 
77
+ const schemaMatch = text.match(/^schemaVersion:\s*(\d+)/m);
78
+ if (schemaMatch) result.schemaVersion = parseInt(schemaMatch[1], 10);
79
+
51
80
  const idMatch = text.match(/^id:\s*(.+)$/m);
52
81
  if (idMatch) result.id = idMatch[1].trim().replace(/^['"]|['"]$/g, '');
53
82
 
@@ -57,12 +86,12 @@ function parseYamlProfile(text) {
57
86
  const descMatch = text.match(/^description:\s*(.+)$/m);
58
87
  if (descMatch) result.description = descMatch[1].trim().replace(/^['"]|['"]$/g, '');
59
88
 
60
- function parseList(field) {
61
- const blockRegex = new RegExp(`^${field}:\\s*\\n((?:[ \\t]+-[^\\n]*\\n?)+)`, 'm');
62
- const match = text.match(blockRegex);
89
+ function parseList(field, sourceText = text) {
90
+ const blockRegex = new RegExp(`^[ \\t]*${field}:\\s*\\n((?:[ \\t]+-[^\\n]*\\n?)+)`, 'm');
91
+ const match = sourceText.match(blockRegex);
63
92
  if (!match) {
64
- const inlineRegex = new RegExp(`^${field}:\\s*\\[([^\\]]*)]`, 'm');
65
- const inlineMatch = text.match(inlineRegex);
93
+ const inlineRegex = new RegExp(`^[ \t]*${field}:\\s*\\[([^\\]]*)]`, 'm');
94
+ const inlineMatch = sourceText.match(inlineRegex);
66
95
  if (!inlineMatch || !inlineMatch[1].trim()) return [];
67
96
  return inlineMatch[1].split(',').map(s => s.trim().replace(/^['"]|['"]$/g, '')).filter(Boolean);
68
97
  }
@@ -72,9 +101,9 @@ function parseYamlProfile(text) {
72
101
  .filter(Boolean);
73
102
  }
74
103
 
75
- function parseMap(field) {
76
- const blockRegex = new RegExp(`^${field}:\\s*\\n((?:[ \\t]+[a-zA-Z0-9_-]+:[^\\n]*\\n?)+)`, 'm');
77
- const match = text.match(blockRegex);
104
+ function parseMap(field, sourceText = text) {
105
+ const blockRegex = new RegExp(`^[ \t]*${field}:\\s*\\n((?:[ \t]+[a-zA-Z0-9_-]+:[^\\n]*\\n?)+)`, 'm');
106
+ const match = sourceText.match(blockRegex);
78
107
  if (!match) return {};
79
108
  const map = {};
80
109
  const lines = match[1].split('\n');
@@ -94,31 +123,201 @@ function parseYamlProfile(text) {
94
123
  return map;
95
124
  }
96
125
 
126
+ function getSectionBlock(sectionKey) {
127
+ const blockRegex = new RegExp(`^${sectionKey}:\\s*\\n((?:[ \t]+[^\\n]*\\n?)+)`, 'm');
128
+ const match = text.match(blockRegex);
129
+ return match ? match[1] : '';
130
+ }
131
+
132
+ // Parse ProfileV2 sections
133
+ const skillsBlock = getSectionBlock('skills');
134
+ if (skillsBlock) {
135
+ result.schemaVersion = 2;
136
+ result.skills.required = parseList('required', skillsBlock);
137
+ result.skills.preferred = parseList('preferred', skillsBlock);
138
+ result.skills.excluded = parseList('excluded', skillsBlock);
139
+ }
140
+
141
+ const policyBlock = getSectionBlock('policy');
142
+ if (policyBlock) {
143
+ result.schemaVersion = 2;
144
+ result.policy = { ...result.policy, ...parseMap('policy', `policy:\n${policyBlock}`) };
145
+ }
146
+
147
+ const genBlock = getSectionBlock('generation');
148
+ if (genBlock) {
149
+ result.schemaVersion = 2;
150
+ result.generation.documents = parseList('documents', genBlock);
151
+ }
152
+
153
+ const techDefaultsBlock = getSectionBlock('technologyDefaults');
154
+ if (techDefaultsBlock) {
155
+ result.schemaVersion = 2;
156
+ result.technologyDefaults = parseMap('technologyDefaults', `technologyDefaults:\n${techDefaultsBlock}`);
157
+ }
158
+
159
+ // Legacy fields
97
160
  result.prefer_skills = parseList('prefer_skills');
98
161
  result.exclude_skills = parseList('exclude_skills');
162
+ result.require_skills = parseList('require_skills');
99
163
  result.generate_docs = parseList('generate_docs');
100
164
  result.skip_docs = parseList('skip_docs');
101
165
  result.enforce = parseMap('enforce');
102
166
  result.defaults = parseMap('defaults');
103
167
 
168
+ // Normalization and bidirectional sync
169
+ if (result.skills.preferred.length === 0 && result.prefer_skills.length > 0) {
170
+ result.skills.preferred = [...result.prefer_skills];
171
+ } else if (result.prefer_skills.length === 0 && result.skills.preferred.length > 0) {
172
+ result.prefer_skills = [...result.skills.preferred];
173
+ }
174
+
175
+ if (result.skills.excluded.length === 0 && result.exclude_skills.length > 0) {
176
+ result.skills.excluded = [...result.exclude_skills];
177
+ } else if (result.exclude_skills.length === 0 && result.skills.excluded.length > 0) {
178
+ result.exclude_skills = [...result.skills.excluded];
179
+ }
180
+
181
+ if (result.skills.required.length === 0 && result.require_skills.length > 0) {
182
+ result.skills.required = [...result.require_skills];
183
+ } else if (result.require_skills.length === 0 && result.skills.required.length > 0) {
184
+ result.require_skills = [...result.skills.required];
185
+ }
186
+
187
+ if (Object.keys(result.technologyDefaults).length === 0 && Object.keys(result.defaults).length > 0) {
188
+ result.technologyDefaults = { ...result.defaults };
189
+ } else if (Object.keys(result.defaults).length === 0 && Object.keys(result.technologyDefaults).length > 0) {
190
+ result.defaults = { ...result.technologyDefaults };
191
+ }
192
+
193
+ if (result.generation.documents.length === 0 && result.generate_docs.length > 0) {
194
+ result.generation.documents = [...result.generate_docs];
195
+ } else if (result.generate_docs.length === 0 && result.generation.documents.length > 0) {
196
+ result.generate_docs = [...result.generation.documents];
197
+ }
198
+
199
+ // Policy <-> Enforce sync
200
+ if (policyBlock) {
201
+ result.enforce.testing = result.policy.testing === 'required' || result.policy.verification === 'required';
202
+ result.enforce.adr = result.policy.adr === 'required';
203
+ result.enforce.code_review = result.policy.review === 'required';
204
+ result.enforce.security_audit = result.policy.security === 'required';
205
+ result.enforce.accessibility_audit = result.policy.accessibility === 'required';
206
+ } else if (Object.keys(result.enforce).length > 0) {
207
+ if (result.enforce.testing !== undefined) {
208
+ result.policy.testing = result.enforce.testing ? 'required' : 'off';
209
+ result.policy.verification = result.enforce.testing ? 'required' : 'off';
210
+ }
211
+ if (result.enforce.adr !== undefined) {
212
+ result.policy.adr = result.enforce.adr ? 'required' : 'off';
213
+ }
214
+ if (result.enforce.code_review !== undefined) {
215
+ result.policy.review = result.enforce.code_review ? 'required' : 'off';
216
+ }
217
+ if (result.enforce.security_audit !== undefined) {
218
+ result.policy.security = result.enforce.security_audit ? 'required' : 'off';
219
+ }
220
+ if (result.enforce.accessibility_audit !== undefined) {
221
+ result.policy.accessibility = result.enforce.accessibility_audit ? 'required' : 'off';
222
+ }
223
+ }
224
+
104
225
  return result;
105
226
  }
106
227
 
228
+ /**
229
+ * Validates a profile configuration for contradictions and unknown skills.
230
+ *
231
+ * @param {ProfileConfig} profile - Profile to validate
232
+ * @param {Object} [registry] - Compiled skill registry
233
+ * @returns {{ valid: boolean }} Validation result
234
+ */
235
+ function validateProfile(profile, registry = null) {
236
+ if (!profile || typeof profile !== 'object') {
237
+ const err = new Error('Invalid profile: expected an object');
238
+ err.code = 'CTX_PROFILE_INVALID';
239
+ throw err;
240
+ }
241
+ if (!profile.id || typeof profile.id !== 'string') {
242
+ const err = new Error('Invalid profile: missing profile id');
243
+ err.code = 'CTX_PROFILE_INVALID';
244
+ throw err;
245
+ }
246
+
247
+ const required = profile.skills?.required || profile.require_skills || [];
248
+ const preferred = profile.skills?.preferred || profile.prefer_skills || [];
249
+ const excluded = profile.skills?.excluded || profile.exclude_skills || [];
250
+
251
+ // Check contradictions between required and excluded
252
+ const excSet = new Set(excluded);
253
+ for (const s of required) {
254
+ if (excSet.has(s)) {
255
+ const err = new Error(`Profile contradiction in '${profile.id}': skill '${s}' cannot be both required and excluded.`);
256
+ err.code = 'CTX_PROFILE_CONTRADICTION';
257
+ err.skill = s;
258
+ throw err;
259
+ }
260
+ }
261
+
262
+ // Check contradictions between preferred and excluded
263
+ for (const s of preferred) {
264
+ if (excSet.has(s)) {
265
+ const err = new Error(`Profile contradiction in '${profile.id}': skill '${s}' cannot be both preferred and excluded.`);
266
+ err.code = 'CTX_PROFILE_CONTRADICTION';
267
+ err.skill = s;
268
+ throw err;
269
+ }
270
+ }
271
+
272
+ // Validate skill IDs against registry if provided
273
+ if (registry && registry.skills) {
274
+ const validSkillIds = new Set(Object.keys(registry.skills));
275
+ const allSkills = [...required, ...preferred, ...excluded];
276
+ for (const s of allSkills) {
277
+ if (!validSkillIds.has(s)) {
278
+ const err = new Error(`Unknown skill ID '${s}' in profile '${profile.id}'. Real registered skill IDs only.`);
279
+ err.code = 'CTX_PROFILE_UNKNOWN_SKILL';
280
+ err.skill = s;
281
+ throw err;
282
+ }
283
+ }
284
+ }
285
+
286
+ // Validate policy values
287
+ const validPolicyValues = new Set(['required', 'recommended', 'off']);
288
+ const policy = profile.policy || {};
289
+ for (const [key, val] of Object.entries(policy)) {
290
+ if (typeof val === 'string' && !validPolicyValues.has(val)) {
291
+ const err = new Error(`Invalid policy value '${val}' for '${key}' in profile '${profile.id}'. Expected: required, recommended, off.`);
292
+ err.code = 'CTX_PROFILE_INVALID_POLICY';
293
+ throw err;
294
+ }
295
+ }
296
+
297
+ return { valid: true };
298
+ }
299
+
107
300
  /**
108
301
  * Lists all available project profiles defined in core/profiles.
109
302
  *
110
303
  * @returns {ProfileConfig[]} Array of profile configurations
111
304
  */
112
305
  function listProfiles() {
113
- if (!fs.existsSync(PROFILES_DIR)) return [];
114
- const files = fs.readdirSync(PROFILES_DIR).filter(f => f.endsWith('.yaml') || f.endsWith('.yml'));
115
- return files.map(file => {
116
- const content = fs.readFileSync(path.join(PROFILES_DIR, file), 'utf8');
117
- const parsed = parseYamlProfile(content);
118
- if (!parsed.id) parsed.id = path.basename(file, path.extname(file));
119
- if (!parsed.name) parsed.name = parsed.id;
120
- return parsed;
121
- });
306
+ const profiles = [];
307
+ const searchDirs = [PROFILES_DIR, path.join(AGENTS_DIR, 'catalog', 'presets')];
308
+
309
+ for (const dir of searchDirs) {
310
+ if (!fs.existsSync(dir)) continue;
311
+ const files = fs.readdirSync(dir).filter(f => f.endsWith('.yaml') || f.endsWith('.yml'));
312
+ for (const file of files) {
313
+ const content = fs.readFileSync(path.join(dir, file), 'utf8');
314
+ const parsed = parseYamlProfile(content);
315
+ if (!parsed.id) parsed.id = path.basename(file, path.extname(file));
316
+ if (!parsed.name) parsed.name = parsed.id;
317
+ profiles.push(parsed);
318
+ }
319
+ }
320
+ return profiles;
122
321
  }
123
322
 
124
323
  /**
@@ -138,48 +337,289 @@ function getProfileLockPath(projectDir = process.cwd()) {
138
337
  return path.join(projectDir, '.agents', 'profile.json');
139
338
  }
140
339
 
141
- function getActiveProfile(projectDir = process.cwd()) {
340
+ /**
341
+ * Gets the active profile, taking package overrides into account when a file or scope is passed.
342
+ *
343
+ * @param {string} [projectDir=process.cwd()] - Project directory
344
+ * @param {string} [targetFileOrScope] - Optional file path or package scope
345
+ * @returns {Object|null} Active profile config or null
346
+ */
347
+ function getActiveProfile(projectDir = process.cwd(), targetFileOrScope = null) {
142
348
  const lockPath = getProfileLockPath(projectDir);
143
349
  if (!fs.existsSync(lockPath)) return null;
350
+ let lockData = null;
144
351
  try {
145
352
  const raw = fs.readFileSync(lockPath, 'utf8');
146
- return JSON.parse(raw);
353
+ lockData = JSON.parse(raw);
147
354
  } catch {
148
355
  return null;
149
356
  }
357
+ if (!lockData) return null;
358
+
359
+ // Check for monorepo package override
360
+ if (targetFileOrScope && lockData.overrides && typeof lockData.overrides === 'object') {
361
+ const normalizedScope = targetFileOrScope.replace(/\\/g, '/').replace(/^\.\//, '');
362
+
363
+ // 1. Direct key match in overrides
364
+ let matchedProfileName = lockData.overrides[normalizedScope];
365
+ let matchedScope = normalizedScope;
366
+
367
+ // 2. Prefix match in overrides
368
+ if (!matchedProfileName) {
369
+ for (const [scopeKey, profName] of Object.entries(lockData.overrides)) {
370
+ const cleanScope = scopeKey.replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/$/, '');
371
+ if (normalizedScope === cleanScope || normalizedScope.startsWith(cleanScope + '/')) {
372
+ matchedProfileName = profName;
373
+ matchedScope = cleanScope;
374
+ break;
375
+ }
376
+ }
377
+ }
378
+
379
+ // 3. Nearest package match via WorkspaceGraphBuilder
380
+ if (!matchedProfileName) {
381
+ try {
382
+ const { WorkspaceGraphBuilder } = require('./workspace/workspace-graph.js');
383
+ const builder = new WorkspaceGraphBuilder();
384
+ const graph = builder.build(projectDir);
385
+ const nearestPkg = builder.findNearestPackage(normalizedScope, graph);
386
+ if (nearestPkg) {
387
+ if (lockData.overrides[nearestPkg.name]) {
388
+ matchedProfileName = lockData.overrides[nearestPkg.name];
389
+ matchedScope = nearestPkg.name;
390
+ } else if (lockData.overrides[nearestPkg.root]) {
391
+ matchedProfileName = lockData.overrides[nearestPkg.root];
392
+ matchedScope = nearestPkg.root;
393
+ }
394
+ }
395
+ } catch {
396
+ // ignore workspace graph failure
397
+ }
398
+ }
399
+
400
+ if (matchedProfileName) {
401
+ const overrideProf = getProfile(matchedProfileName);
402
+ if (overrideProf) {
403
+ return {
404
+ ...overrideProf,
405
+ profile: overrideProf.id,
406
+ isOverride: true,
407
+ scope: matchedScope,
408
+ rootProfile: lockData.profile,
409
+ overrides: lockData.overrides,
410
+ };
411
+ }
412
+ }
413
+ }
414
+
415
+ return lockData;
150
416
  }
151
417
 
152
- function applyProfile(profileName, projectDir = process.cwd()) {
418
+ /**
419
+ * Applies a profile to the project, supporting root apply or package-scoped overrides.
420
+ *
421
+ * @param {string} profileName - Profile slug or name
422
+ * @param {string} [projectDir=process.cwd()] - Project directory
423
+ * @param {Object} [options={}] - Options (scope, noExport)
424
+ * @returns {Object} Updated profile lock data
425
+ */
426
+ function applyProfile(profileName, projectDir = process.cwd(), options = {}) {
153
427
  const profile = getProfile(profileName);
154
428
  if (!profile) {
155
429
  throw new Error(`Profile '${profileName}' not found. Available profiles: ${listProfiles().map(p => p.id).join(', ')}`);
156
430
  }
157
431
 
432
+ validateProfile(profile);
433
+
158
434
  const lockPath = getProfileLockPath(projectDir);
159
435
  fs.mkdirSync(path.dirname(lockPath), { recursive: true });
160
436
 
161
- const lockData = {
162
- profile: profile.id,
163
- name: profile.name,
164
- description: profile.description,
165
- exclude_skills: profile.exclude_skills || [],
166
- prefer_skills: profile.prefer_skills || [],
167
- enforce: profile.enforce || {},
168
- defaults: profile.defaults || {},
169
- appliedAt: new Date().toISOString(),
170
- };
437
+ let existing = {};
438
+ if (fs.existsSync(lockPath)) {
439
+ try {
440
+ existing = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
441
+ } catch {
442
+ existing = {};
443
+ }
444
+ }
445
+
446
+ let lockData;
447
+ if (options.scope) {
448
+ const scope = options.scope.replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/$/, '');
449
+ const overrides = existing.overrides || {};
450
+ overrides[scope] = profile.id;
451
+
452
+ lockData = {
453
+ ...existing,
454
+ overrides,
455
+ updatedAt: new Date().toISOString(),
456
+ };
457
+ if (!lockData.profile) {
458
+ lockData.profile = profile.id;
459
+ lockData.name = profile.name;
460
+ lockData.description = profile.description;
461
+ lockData.skills = profile.skills;
462
+ lockData.policy = profile.policy;
463
+ lockData.generation = profile.generation;
464
+ lockData.technologyDefaults = profile.technologyDefaults;
465
+ lockData.exclude_skills = profile.exclude_skills || [];
466
+ lockData.prefer_skills = profile.prefer_skills || [];
467
+ lockData.require_skills = profile.require_skills || [];
468
+ lockData.enforce = profile.enforce || {};
469
+ lockData.defaults = profile.defaults || {};
470
+ }
471
+ } else {
472
+ lockData = {
473
+ schemaVersion: 2,
474
+ profile: profile.id,
475
+ name: profile.name,
476
+ description: profile.description,
477
+ skills: profile.skills || {
478
+ required: profile.require_skills || [],
479
+ preferred: profile.prefer_skills || [],
480
+ excluded: profile.exclude_skills || [],
481
+ },
482
+ policy: profile.policy || {},
483
+ generation: profile.generation || {
484
+ documents: profile.generate_docs || [],
485
+ },
486
+ technologyDefaults: profile.technologyDefaults || profile.defaults || {},
487
+ exclude_skills: profile.exclude_skills || [],
488
+ prefer_skills: profile.prefer_skills || [],
489
+ require_skills: profile.require_skills || [],
490
+ enforce: profile.enforce || {},
491
+ defaults: profile.defaults || {},
492
+ overrides: existing.overrides || {},
493
+ appliedAt: new Date().toISOString(),
494
+ };
495
+ }
496
+
497
+ const lock = new ProjectMutationLock(projectDir);
498
+ const lockToken = lock.acquire({ command: `profile apply ${profile.id}` });
499
+
500
+ try {
501
+ const tx = new JournaledTransaction(projectDir);
502
+ tx.stageWrite(path.relative(projectDir, lockPath).replace(/\\/g, '/'), JSON.stringify(lockData, null, 2) + '\n');
503
+ tx.commit();
504
+ } finally {
505
+ lock.release(lockToken);
506
+ }
171
507
 
172
- fs.writeFileSync(lockPath, JSON.stringify(lockData, null, 2) + '\n');
173
508
  return lockData;
174
509
  }
175
510
 
176
- function removeActiveProfile(projectDir = process.cwd()) {
511
+ /**
512
+ * Removes active profile lock or a specific package override.
513
+ *
514
+ * @param {string} [projectDir=process.cwd()] - Project directory
515
+ * @param {Object} [options={}] - Options (scope)
516
+ * @returns {boolean} True if removed
517
+ */
518
+ function removeActiveProfile(projectDir = process.cwd(), options = {}) {
177
519
  const lockPath = getProfileLockPath(projectDir);
178
- if (fs.existsSync(lockPath)) {
179
- fs.unlinkSync(lockPath);
180
- return true;
520
+ if (!fs.existsSync(lockPath)) return false;
521
+
522
+ if (options && options.scope) {
523
+ try {
524
+ const lockData = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
525
+ const cleanScope = options.scope.replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/$/, '');
526
+ if (lockData.overrides && lockData.overrides[cleanScope]) {
527
+ delete lockData.overrides[cleanScope];
528
+
529
+ const lock = new ProjectMutationLock(projectDir);
530
+ const lockToken = lock.acquire({ command: 'profile remove override' });
531
+ try {
532
+ const tx = new JournaledTransaction(projectDir);
533
+ tx.stageWrite(path.relative(projectDir, lockPath).replace(/\\/g, '/'), JSON.stringify(lockData, null, 2) + '\n');
534
+ tx.commit();
535
+ } finally {
536
+ lock.release(lockToken);
537
+ }
538
+ return true;
539
+ }
540
+ } catch {
541
+ return false;
542
+ }
543
+ return false;
544
+ }
545
+
546
+ const lock = new ProjectMutationLock(projectDir);
547
+ const lockToken = lock.acquire({ command: 'profile remove' });
548
+ try {
549
+ const tx = new JournaledTransaction(projectDir);
550
+ tx.stageDelete(path.relative(projectDir, lockPath).replace(/\\/g, '/'));
551
+ tx.commit();
552
+ } finally {
553
+ lock.release(lockToken);
554
+ }
555
+ return true;
556
+ }
557
+
558
+ /**
559
+ * Returns structured explanation of a profile.
560
+ *
561
+ * @param {string} profileName - Profile slug or name
562
+ * @param {string} [projectDir=process.cwd()] - Project directory
563
+ * @returns {Object} Explanation structure
564
+ */
565
+ function explainProfile(profileName, projectDir = process.cwd()) {
566
+ const profile = getProfile(profileName);
567
+ if (!profile) {
568
+ throw new Error(`Profile '${profileName}' not found. Available profiles: ${listProfiles().map(p => p.id).join(', ')}`);
181
569
  }
182
- return false;
570
+
571
+ return {
572
+ schemaVersion: profile.schemaVersion || 2,
573
+ id: profile.id,
574
+ name: profile.name,
575
+ description: profile.description,
576
+ skills: {
577
+ required: profile.skills?.required || profile.require_skills || [],
578
+ preferred: profile.skills?.preferred || profile.prefer_skills || [],
579
+ excluded: profile.skills?.excluded || profile.exclude_skills || [],
580
+ },
581
+ policy: profile.policy || {},
582
+ technologyDefaults: profile.technologyDefaults || profile.defaults || {},
583
+ generation: {
584
+ documents: profile.generation?.documents || profile.generate_docs || [],
585
+ },
586
+ };
587
+ }
588
+
589
+ /**
590
+ * Formats structured profile explanation into human-readable text.
591
+ *
592
+ * @param {Object} explanation - Profile explanation object
593
+ * @returns {string} Formatted text
594
+ */
595
+ function formatProfileExplanation(explanation) {
596
+ const lines = [];
597
+ lines.push(`Profile: ${explanation.name} (${explanation.id})`);
598
+ lines.push(`Description: ${explanation.description}`);
599
+ lines.push('');
600
+ lines.push('Skills:');
601
+ lines.push(` Required: ${explanation.skills.required.length > 0 ? explanation.skills.required.join(', ') : 'none'}`);
602
+ lines.push(` Preferred: ${explanation.skills.preferred.length > 0 ? explanation.skills.preferred.join(', ') : 'none'}`);
603
+ lines.push(` Excluded: ${explanation.skills.excluded.length > 0 ? explanation.skills.excluded.join(', ') : 'none'}`);
604
+ lines.push('');
605
+ lines.push('Policy:');
606
+ for (const [key, val] of Object.entries(explanation.policy)) {
607
+ lines.push(` ${(key.charAt(0).toUpperCase() + key.slice(1)).padEnd(14)}: ${val}`);
608
+ }
609
+ lines.push('');
610
+ lines.push('Technology Defaults:');
611
+ const techKeys = Object.keys(explanation.technologyDefaults);
612
+ if (techKeys.length === 0) {
613
+ lines.push(' none');
614
+ } else {
615
+ for (const [key, val] of Object.entries(explanation.technologyDefaults)) {
616
+ lines.push(` ${key.padEnd(14)}: ${val}`);
617
+ }
618
+ }
619
+ lines.push('');
620
+ lines.push('Generation Documents:');
621
+ lines.push(` ${explanation.generation.documents.join(', ') || 'none'}`);
622
+ return lines.join('\n');
183
623
  }
184
624
 
185
625
  /**
@@ -263,18 +703,10 @@ function detectStack(projectDir = process.cwd()) {
263
703
  }
264
704
 
265
705
  // Infer recommended profile
266
- let recommendedProfile = 'startup';
706
+ let recommendedProfile = 'init';
267
707
  const hasFrontend = detected.some(d => ['React', 'Next.js', 'Tailwind CSS'].includes(d));
268
708
  const hasBackend = detected.some(d => ['Node.js Backend', 'NestJS', 'Python'].includes(d));
269
709
 
270
- if (hasFrontend && !hasBackend) {
271
- recommendedProfile = 'frontend';
272
- } else if (!hasFrontend && hasBackend) {
273
- recommendedProfile = 'backend';
274
- } else if (hasFrontend && hasBackend) {
275
- recommendedProfile = 'startup';
276
- }
277
-
278
710
  return {
279
711
  detected,
280
712
  recommendedProfile,
@@ -290,4 +722,7 @@ module.exports = {
290
722
  removeActiveProfile,
291
723
  detectStack,
292
724
  parseYamlProfile,
725
+ validateProfile,
726
+ explainProfile,
727
+ formatProfileExplanation,
293
728
  };