contextos-agents 1.7.0 → 2.0.0-beta.2

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 (309) hide show
  1. package/.agents/AGENTS.md +1 -1
  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 +80 -101
  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 +574 -111
  26. package/.agents/customization-dx.js +282 -0
  27. package/.agents/doctor.js +719 -64
  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 +259 -60
  38. package/.agents/profiles.js +486 -51
  39. package/.agents/resolver.js +50 -534
  40. package/.agents/schemas/attestation.review.v1.json +111 -0
  41. package/.agents/schemas/attestation.verification.v1.json +85 -0
  42. package/.agents/schemas/lockfile.v2.schema.json +134 -0
  43. package/.agents/schemas/profile.v2.schema.json +114 -0
  44. package/.agents/schemas/runtime.thread.v1.json +192 -0
  45. package/.agents/schemas/skill.manifest.v2.json +177 -0
  46. package/.agents/schemas/verification.spec.v1.json +39 -0
  47. package/.agents/schemas/workspace.graph.schema.json +106 -0
  48. package/.agents/transaction-core/event-store.js +288 -0
  49. package/.agents/transaction-core/idempotency.js +129 -0
  50. package/.agents/transaction-core/ipc-lock.js +311 -0
  51. package/.agents/transaction-core/plugin-supply-chain-bundle.js +436 -0
  52. package/.agents/validate.js +44 -1
  53. package/.agents/watch.js +354 -102
  54. package/.agents/workspace/workspace-graph.js +778 -0
  55. package/README.md +59 -387
  56. package/benchmarks/v2/analysis/statistics.js +140 -0
  57. package/benchmarks/v2/analysis/stats.js +69 -0
  58. package/benchmarks/v2/arms/arm-definitions.js +79 -0
  59. package/benchmarks/v2/dataset.schema.json +34 -0
  60. package/benchmarks/v2/evaluators/index.js +25 -0
  61. package/benchmarks/v2/evaluators/verified-success.js +116 -0
  62. package/benchmarks/v2/harness/runner.js +88 -0
  63. package/benchmarks/v2/pilot-tasks.json +392 -0
  64. package/bin/commands/recover.js +88 -0
  65. package/bin/commands/update.js +80 -17
  66. package/bin/commands.js +62 -25
  67. package/bin/index.js +138 -81
  68. package/bin/lib/lockfile.js +5 -3
  69. package/bin/lib/safe-writer.js +34 -3
  70. package/package.json +85 -72
  71. package/registry.json +2 -2
  72. package/registry.v2.schema.json +86 -0
  73. package/.agents/core/profiles/backend.yaml +0 -47
  74. package/.agents/core/profiles/enterprise.yaml +0 -46
  75. package/.agents/core/profiles/frontend.yaml +0 -46
  76. package/.agents/core/profiles/hackathon.yaml +0 -45
  77. package/.agents/core/profiles/mvp.yaml +0 -44
  78. package/.agents/core/profiles/startup.yaml +0 -48
  79. package/.agents/core/skills/adapters/EXAMPLES.md +0 -19
  80. package/.agents/core/skills/adapters/SKILL.md +0 -105
  81. package/.agents/core/skills/adapters/TROUBLESHOOTING.md +0 -7
  82. package/.agents/core/skills/adapters/VALIDATION.json +0 -12
  83. package/.agents/core/skills/adapters/skill.yaml +0 -16
  84. package/.agents/core/skills/architecture-diagrams/SKILL.md +0 -108
  85. package/.agents/core/skills/architecture-diagrams/VALIDATION.json +0 -12
  86. package/.agents/core/skills/architecture-diagrams/skill.yaml +0 -12
  87. package/.agents/core/skills/brutalist-design/SKILL.md +0 -150
  88. package/.agents/core/skills/brutalist-design/VALIDATION.json +0 -12
  89. package/.agents/core/skills/brutalist-design/skill.yaml +0 -12
  90. package/.agents/core/skills/database/EXAMPLES.md +0 -74
  91. package/.agents/core/skills/database/SKILL.md +0 -101
  92. package/.agents/core/skills/database/TROUBLESHOOTING.md +0 -18
  93. package/.agents/core/skills/database/VALIDATION.json +0 -11
  94. package/.agents/core/skills/database/skill.yaml +0 -31
  95. package/.agents/core/skills/ddd/EXAMPLES.md +0 -42
  96. package/.agents/core/skills/ddd/SKILL.md +0 -247
  97. package/.agents/core/skills/ddd/TROUBLESHOOTING.md +0 -19
  98. package/.agents/core/skills/ddd/VALIDATION.json +0 -12
  99. package/.agents/core/skills/ddd/ddd.md +0 -178
  100. package/.agents/core/skills/ddd/skill.yaml +0 -17
  101. package/.agents/core/skills/decisions/EXAMPLES.md +0 -35
  102. package/.agents/core/skills/decisions/SKILL.md +0 -90
  103. package/.agents/core/skills/decisions/TROUBLESHOOTING.md +0 -13
  104. package/.agents/core/skills/decisions/VALIDATION.json +0 -12
  105. package/.agents/core/skills/decisions/skill.yaml +0 -16
  106. package/.agents/core/skills/docker/EXAMPLES.md +0 -56
  107. package/.agents/core/skills/docker/SKILL.md +0 -63
  108. package/.agents/core/skills/docker/TROUBLESHOOTING.md +0 -18
  109. package/.agents/core/skills/docker/VALIDATION.json +0 -11
  110. package/.agents/core/skills/docker/skill.yaml +0 -29
  111. package/.agents/core/skills/fastapi/EXAMPLES.md +0 -36
  112. package/.agents/core/skills/fastapi/SKILL.md +0 -148
  113. package/.agents/core/skills/fastapi/TROUBLESHOOTING.md +0 -19
  114. package/.agents/core/skills/fastapi/VALIDATION.json +0 -12
  115. package/.agents/core/skills/fastapi/fastapi.md +0 -112
  116. package/.agents/core/skills/fastapi/skill.yaml +0 -17
  117. package/.agents/core/skills/generators/EXAMPLES.md +0 -19
  118. package/.agents/core/skills/generators/SKILL.md +0 -112
  119. package/.agents/core/skills/generators/TROUBLESHOOTING.md +0 -7
  120. package/.agents/core/skills/generators/VALIDATION.json +0 -12
  121. package/.agents/core/skills/generators/skill.yaml +0 -25
  122. package/.agents/core/skills/generators/templates/API.md +0 -77
  123. package/.agents/core/skills/generators/templates/ARCHITECTURE.md +0 -70
  124. package/.agents/core/skills/generators/templates/DATABASE.md +0 -42
  125. package/.agents/core/skills/generators/templates/DECISION.md +0 -46
  126. package/.agents/core/skills/generators/templates/PRD.md +0 -67
  127. package/.agents/core/skills/generators/templates/PROJECT_GRAPH.md +0 -56
  128. package/.agents/core/skills/generators/templates/ROADMAP.md +0 -51
  129. package/.agents/core/skills/generators/templates/TASKS.md +0 -43
  130. package/.agents/core/skills/generators/templates/UI.md +0 -73
  131. package/.agents/core/skills/graphify/EXAMPLES.md +0 -73
  132. package/.agents/core/skills/graphify/SKILL.md +0 -130
  133. package/.agents/core/skills/graphify/VALIDATION.json +0 -12
  134. package/.agents/core/skills/graphify/skill.yaml +0 -18
  135. package/.agents/core/skills/impeccable-design/EXAMPLES.md +0 -26
  136. package/.agents/core/skills/impeccable-design/SKILL.md +0 -201
  137. package/.agents/core/skills/impeccable-design/TROUBLESHOOTING.md +0 -19
  138. package/.agents/core/skills/impeccable-design/VALIDATION.json +0 -12
  139. package/.agents/core/skills/impeccable-design/skill.yaml +0 -20
  140. package/.agents/core/skills/interview-me/SKILL.md +0 -97
  141. package/.agents/core/skills/interview-me/VALIDATION.json +0 -12
  142. package/.agents/core/skills/interview-me/skill.yaml +0 -12
  143. package/.agents/core/skills/microservices/EXAMPLES.md +0 -38
  144. package/.agents/core/skills/microservices/SKILL.md +0 -164
  145. package/.agents/core/skills/microservices/TROUBLESHOOTING.md +0 -19
  146. package/.agents/core/skills/microservices/VALIDATION.json +0 -12
  147. package/.agents/core/skills/microservices/microservices.md +0 -119
  148. package/.agents/core/skills/microservices/skill.yaml +0 -17
  149. package/.agents/core/skills/minimalist-design/SKILL.md +0 -113
  150. package/.agents/core/skills/minimalist-design/VALIDATION.json +0 -12
  151. package/.agents/core/skills/minimalist-design/skill.yaml +0 -12
  152. package/.agents/core/skills/nestjs/EXAMPLES.md +0 -40
  153. package/.agents/core/skills/nestjs/SKILL.md +0 -139
  154. package/.agents/core/skills/nestjs/TROUBLESHOOTING.md +0 -19
  155. package/.agents/core/skills/nestjs/VALIDATION.json +0 -12
  156. package/.agents/core/skills/nestjs/nestjs.md +0 -103
  157. package/.agents/core/skills/nestjs/skill.yaml +0 -17
  158. package/.agents/core/skills/nextjs/EXAMPLES.md +0 -40
  159. package/.agents/core/skills/nextjs/SKILL.md +0 -163
  160. package/.agents/core/skills/nextjs/TROUBLESHOOTING.md +0 -19
  161. package/.agents/core/skills/nextjs/VALIDATION.json +0 -12
  162. package/.agents/core/skills/nextjs/nextjs.md +0 -67
  163. package/.agents/core/skills/nextjs/skill.yaml +0 -17
  164. package/.agents/core/skills/node/EXAMPLES.md +0 -80
  165. package/.agents/core/skills/node/SKILL.md +0 -128
  166. package/.agents/core/skills/node/TROUBLESHOOTING.md +0 -19
  167. package/.agents/core/skills/node/VALIDATION.json +0 -12
  168. package/.agents/core/skills/node/node.md +0 -87
  169. package/.agents/core/skills/node/skill.yaml +0 -17
  170. package/.agents/core/skills/performance/EXAMPLES.md +0 -30
  171. package/.agents/core/skills/performance/SKILL.md +0 -75
  172. package/.agents/core/skills/performance/TROUBLESHOOTING.md +0 -19
  173. package/.agents/core/skills/performance/VALIDATION.json +0 -12
  174. package/.agents/core/skills/performance/performance.md +0 -52
  175. package/.agents/core/skills/performance/skill.yaml +0 -17
  176. package/.agents/core/skills/react/EXAMPLES.md +0 -79
  177. package/.agents/core/skills/react/SKILL.md +0 -132
  178. package/.agents/core/skills/react/TROUBLESHOOTING.md +0 -19
  179. package/.agents/core/skills/react/VALIDATION.json +0 -12
  180. package/.agents/core/skills/react/react.md +0 -93
  181. package/.agents/core/skills/react/skill.yaml +0 -17
  182. package/.agents/core/skills/react-best-practices/SKILL.md +0 -155
  183. package/.agents/core/skills/react-best-practices/VALIDATION.json +0 -12
  184. package/.agents/core/skills/react-best-practices/skill.yaml +0 -14
  185. package/.agents/core/skills/redesign-audit/SKILL.md +0 -117
  186. package/.agents/core/skills/redesign-audit/VALIDATION.json +0 -12
  187. package/.agents/core/skills/redesign-audit/skill.yaml +0 -12
  188. package/.agents/core/skills/soft-design/SKILL.md +0 -108
  189. package/.agents/core/skills/soft-design/VALIDATION.json +0 -12
  190. package/.agents/core/skills/soft-design/skill.yaml +0 -12
  191. package/.agents/core/skills/state-management/EXAMPLES.md +0 -56
  192. package/.agents/core/skills/state-management/SKILL.md +0 -48
  193. package/.agents/core/skills/state-management/TROUBLESHOOTING.md +0 -18
  194. package/.agents/core/skills/state-management/VALIDATION.json +0 -11
  195. package/.agents/core/skills/state-management/skill.yaml +0 -28
  196. package/.agents/core/skills/subagent-orchestrator/SKILL.md +0 -117
  197. package/.agents/core/skills/subagent-orchestrator/VALIDATION.json +0 -12
  198. package/.agents/core/skills/subagent-orchestrator/skill.yaml +0 -12
  199. package/.agents/core/skills/system-design/EXAMPLES.md +0 -75
  200. package/.agents/core/skills/system-design/SKILL.md +0 -419
  201. package/.agents/core/skills/system-design/TROUBLESHOOTING.md +0 -19
  202. package/.agents/core/skills/system-design/VALIDATION.json +0 -12
  203. package/.agents/core/skills/system-design/skill.yaml +0 -20
  204. package/.agents/core/skills/system-design/system-design.md +0 -112
  205. package/.agents/core/skills/testing/EXAMPLES.md +0 -71
  206. package/.agents/core/skills/testing/SKILL.md +0 -70
  207. package/.agents/core/skills/testing/TROUBLESHOOTING.md +0 -18
  208. package/.agents/core/skills/testing/VALIDATION.json +0 -11
  209. package/.agents/core/skills/testing/skill.yaml +0 -32
  210. package/.agents/core/skills/typescript/EXAMPLES.md +0 -64
  211. package/.agents/core/skills/typescript/SKILL.md +0 -112
  212. package/.agents/core/skills/typescript/TROUBLESHOOTING.md +0 -19
  213. package/.agents/core/skills/typescript/VALIDATION.json +0 -12
  214. package/.agents/core/skills/typescript/skill.yaml +0 -17
  215. package/.agents/core/skills/typescript/typescript.md +0 -71
  216. package/.agents/core/skills/ui-design/EXAMPLES.md +0 -21
  217. package/.agents/core/skills/ui-design/SKILL.md +0 -124
  218. package/.agents/core/skills/ui-design/TROUBLESHOOTING.md +0 -19
  219. package/.agents/core/skills/ui-design/VALIDATION.json +0 -12
  220. package/.agents/core/skills/ui-design/skill.yaml +0 -17
  221. package/.agents/core/skills/ui-design/ui.md +0 -88
  222. package/.agents/core/skills/ui-ux-pro/EXAMPLES.md +0 -62
  223. package/.agents/core/skills/ui-ux-pro/SKILL.md +0 -375
  224. package/.agents/core/skills/ui-ux-pro/TROUBLESHOOTING.md +0 -19
  225. package/.agents/core/skills/ui-ux-pro/VALIDATION.json +0 -12
  226. package/.agents/core/skills/ui-ux-pro/skill.yaml +0 -19
  227. package/.agents/core/skills/ux-design/EXAMPLES.md +0 -36
  228. package/.agents/core/skills/ux-design/SKILL.md +0 -116
  229. package/.agents/core/skills/ux-design/TROUBLESHOOTING.md +0 -19
  230. package/.agents/core/skills/ux-design/VALIDATION.json +0 -12
  231. package/.agents/core/skills/ux-design/skill.yaml +0 -17
  232. package/.agents/core/skills/ux-design/ux.md +0 -80
  233. package/.agents/core/skills/vercel-optimize/SKILL.md +0 -83
  234. package/.agents/core/skills/vercel-optimize/VALIDATION.json +0 -12
  235. package/.agents/core/skills/vercel-optimize/scripts/collect-signals.mjs +0 -131
  236. package/.agents/core/skills/vercel-optimize/scripts/gate-investigations.mjs +0 -142
  237. package/.agents/core/skills/vercel-optimize/scripts/merge-signals.mjs +0 -143
  238. package/.agents/core/skills/vercel-optimize/scripts/scan-codebase.mjs +0 -174
  239. package/.agents/core/skills/vercel-optimize/skill.yaml +0 -18
  240. package/.agents/core/skills/web-accessibility/EXAMPLES.md +0 -39
  241. package/.agents/core/skills/web-accessibility/SKILL.md +0 -170
  242. package/.agents/core/skills/web-accessibility/TROUBLESHOOTING.md +0 -19
  243. package/.agents/core/skills/web-accessibility/VALIDATION.json +0 -12
  244. package/.agents/core/skills/web-accessibility/accessibility.md +0 -63
  245. package/.agents/core/skills/web-accessibility/skill.yaml +0 -17
  246. package/.agents/generated/claude/skills/adapters/SKILL.md +0 -126
  247. package/.agents/generated/claude/skills/architecture-diagrams/SKILL.md +0 -101
  248. package/.agents/generated/claude/skills/brutalist-design/SKILL.md +0 -145
  249. package/.agents/generated/claude/skills/database/SKILL.md +0 -191
  250. package/.agents/generated/claude/skills/ddd/SKILL.md +0 -305
  251. package/.agents/generated/claude/skills/decisions/SKILL.md +0 -134
  252. package/.agents/generated/claude/skills/docker/SKILL.md +0 -135
  253. package/.agents/generated/claude/skills/fastapi/SKILL.md +0 -200
  254. package/.agents/generated/claude/skills/generators/SKILL.md +0 -133
  255. package/.agents/generated/claude/skills/graphify/SKILL.md +0 -198
  256. package/.agents/generated/claude/skills/impeccable-design/SKILL.md +0 -241
  257. package/.agents/generated/claude/skills/interview-me/SKILL.md +0 -90
  258. package/.agents/generated/claude/skills/microservices/SKILL.md +0 -218
  259. package/.agents/generated/claude/skills/minimalist-design/SKILL.md +0 -108
  260. package/.agents/generated/claude/skills/nestjs/SKILL.md +0 -195
  261. package/.agents/generated/claude/skills/nextjs/SKILL.md +0 -219
  262. package/.agents/generated/claude/skills/node/SKILL.md +0 -224
  263. package/.agents/generated/claude/skills/performance/SKILL.md +0 -121
  264. package/.agents/generated/claude/skills/react/SKILL.md +0 -227
  265. package/.agents/generated/claude/skills/react-best-practices/SKILL.md +0 -146
  266. package/.agents/generated/claude/skills/redesign-audit/SKILL.md +0 -112
  267. package/.agents/generated/claude/skills/soft-design/SKILL.md +0 -103
  268. package/.agents/generated/claude/skills/state-management/SKILL.md +0 -120
  269. package/.agents/generated/claude/skills/subagent-orchestrator/SKILL.md +0 -110
  270. package/.agents/generated/claude/skills/system-design/SKILL.md +0 -507
  271. package/.agents/generated/claude/skills/testing/SKILL.md +0 -157
  272. package/.agents/generated/claude/skills/typescript/SKILL.md +0 -192
  273. package/.agents/generated/claude/skills/ui-design/SKILL.md +0 -161
  274. package/.agents/generated/claude/skills/ui-ux-pro/SKILL.md +0 -451
  275. package/.agents/generated/claude/skills/ux-design/SKILL.md +0 -168
  276. package/.agents/generated/claude/skills/vercel-optimize/SKILL.md +0 -76
  277. package/.agents/generated/claude/skills/web-accessibility/SKILL.md +0 -225
  278. package/.agents/generated/gemini/skills/adapters/SKILL.md +0 -135
  279. package/.agents/generated/gemini/skills/architecture-diagrams/SKILL.md +0 -107
  280. package/.agents/generated/gemini/skills/brutalist-design/SKILL.md +0 -151
  281. package/.agents/generated/gemini/skills/database/SKILL.md +0 -200
  282. package/.agents/generated/gemini/skills/ddd/SKILL.md +0 -314
  283. package/.agents/generated/gemini/skills/decisions/SKILL.md +0 -143
  284. package/.agents/generated/gemini/skills/docker/SKILL.md +0 -144
  285. package/.agents/generated/gemini/skills/fastapi/SKILL.md +0 -209
  286. package/.agents/generated/gemini/skills/generators/SKILL.md +0 -142
  287. package/.agents/generated/gemini/skills/graphify/SKILL.md +0 -205
  288. package/.agents/generated/gemini/skills/impeccable-design/SKILL.md +0 -250
  289. package/.agents/generated/gemini/skills/interview-me/SKILL.md +0 -96
  290. package/.agents/generated/gemini/skills/microservices/SKILL.md +0 -227
  291. package/.agents/generated/gemini/skills/minimalist-design/SKILL.md +0 -114
  292. package/.agents/generated/gemini/skills/nestjs/SKILL.md +0 -204
  293. package/.agents/generated/gemini/skills/nextjs/SKILL.md +0 -298
  294. package/.agents/generated/gemini/skills/node/SKILL.md +0 -323
  295. package/.agents/generated/gemini/skills/performance/SKILL.md +0 -185
  296. package/.agents/generated/gemini/skills/react/SKILL.md +0 -332
  297. package/.agents/generated/gemini/skills/react-best-practices/SKILL.md +0 -152
  298. package/.agents/generated/gemini/skills/redesign-audit/SKILL.md +0 -118
  299. package/.agents/generated/gemini/skills/soft-design/SKILL.md +0 -109
  300. package/.agents/generated/gemini/skills/state-management/SKILL.md +0 -129
  301. package/.agents/generated/gemini/skills/subagent-orchestrator/SKILL.md +0 -116
  302. package/.agents/generated/gemini/skills/system-design/SKILL.md +0 -631
  303. package/.agents/generated/gemini/skills/testing/SKILL.md +0 -166
  304. package/.agents/generated/gemini/skills/typescript/SKILL.md +0 -275
  305. package/.agents/generated/gemini/skills/ui-design/SKILL.md +0 -170
  306. package/.agents/generated/gemini/skills/ui-ux-pro/SKILL.md +0 -460
  307. package/.agents/generated/gemini/skills/ux-design/SKILL.md +0 -177
  308. package/.agents/generated/gemini/skills/vercel-optimize/SKILL.md +0 -82
  309. 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
  };