session-orchestrator 4.1.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/.agents/skills/session-plan/SKILL.md +1 -1
  2. package/.agents/skills/session-start/SKILL.md +1 -1
  3. package/.agents/skills/ux-grill/SKILL.md +22 -0
  4. package/.claude-plugin/marketplace.json +1 -1
  5. package/.claude-plugin/plugin.json +3 -2
  6. package/.codex-plugin/plugin.json +1 -1
  7. package/.codex-plugin/skills/session-plan/SKILL.md +1 -1
  8. package/.codex-plugin/skills/session-start/SKILL.md +1 -1
  9. package/.codex-plugin/skills/ux-grill/SKILL.md +21 -0
  10. package/.codex-plugin/skills/ux-grill/agents/openai.yaml +5 -0
  11. package/.cursor/commands/ux-grill.md +14 -0
  12. package/.cursor/skills/session-plan/SKILL.md +1 -1
  13. package/.cursor/skills/session-start/SKILL.md +1 -1
  14. package/.cursor/skills/ux-grill/SKILL.md +13 -0
  15. package/.cursor-plugin/plugin.json +1 -1
  16. package/AGENTS.md +2 -1
  17. package/CHANGELOG.md +128 -1
  18. package/README.md +98 -86
  19. package/agents/dialectic-deriver.md +11 -0
  20. package/agents/ux-evaluator.md +1 -1
  21. package/commands/close.md +3 -3
  22. package/commands/go.md +2 -0
  23. package/commands/memory-cleanup.md +4 -3
  24. package/commands/persona-panel.md +1 -1
  25. package/commands/session.md +3 -2
  26. package/commands/ux-grill.md +51 -0
  27. package/docs/README.md +4 -4
  28. package/docs/USER-GUIDE.md +117 -50
  29. package/docs/agent-authoring.md +2 -2
  30. package/docs/baseline.md +55 -1
  31. package/docs/ci-setup.md +1 -1
  32. package/docs/codex-setup.md +9 -0
  33. package/docs/components.md +9 -9
  34. package/docs/cursor-setup.md +1 -0
  35. package/docs/events-schema.md +13 -6
  36. package/docs/github-mirror-protection.md +61 -20
  37. package/docs/instruction-delivery.md +1 -1
  38. package/docs/memory-proposal-flow.md +3 -3
  39. package/docs/migration-v4.md +2 -2
  40. package/docs/migration-v5.md +62 -0
  41. package/docs/owner-config-schema.md +74 -90
  42. package/docs/persona-panel.md +4 -4
  43. package/docs/pi-setup.md +1 -0
  44. package/docs/rule-authoring.md +13 -6
  45. package/docs/scope-collision-guard.md +16 -0
  46. package/docs/session-config-reference.md +55 -22
  47. package/docs/session-config-template.md +9 -5
  48. package/docs/vault-docs-architecture.md +4 -2
  49. package/hooks/_lib/hook-import-set.json +70 -3
  50. package/hooks/_lib/lock-bootstrap.mjs +84 -1
  51. package/hooks/_lib/vcs-create-matcher.mjs +401 -16
  52. package/hooks/enforce-scope.mjs +201 -0
  53. package/hooks/hooks-codex.json +1 -1
  54. package/hooks/hooks-cursor.json +5 -0
  55. package/hooks/hooks.json +7 -2
  56. package/hooks/on-session-start.mjs +171 -49
  57. package/hooks/post-bash-issue-budget-refund.mjs +375 -0
  58. package/hooks/pre-auq-clarity.mjs +70 -18
  59. package/hooks/pre-bash-issue-budget.mjs +170 -26
  60. package/hooks/subagent-telemetry.mjs +106 -20
  61. package/package.json +5 -4
  62. package/pi/prompts/ux-grill.md +12 -0
  63. package/scripts/baseline-archetypes.mjs +28 -0
  64. package/scripts/ci/assert-vitest-green.mjs +4 -2
  65. package/scripts/dialectic-deriver.mjs +32 -8
  66. package/scripts/emit-session.mjs +72 -1
  67. package/scripts/lib/agent-status.mjs +441 -9
  68. package/scripts/lib/auq/schema.mjs +10 -3
  69. package/scripts/lib/auto-dialectic.mjs +0 -68
  70. package/scripts/lib/baseline-archetypes.mjs +439 -0
  71. package/scripts/lib/build-live-signals.mjs +5 -6
  72. package/scripts/lib/ci-status-banner.mjs +29 -6
  73. package/scripts/lib/claude-md-budget-lint.mjs +52 -2
  74. package/scripts/lib/config/issue-budget.mjs +68 -8
  75. package/scripts/lib/config/private-config-dir.mjs +3 -2
  76. package/scripts/lib/config/remote-hosts.mjs +2 -2
  77. package/scripts/lib/config-schema.mjs +79 -0
  78. package/scripts/lib/config.mjs +12 -1
  79. package/scripts/lib/eval/engine.mjs +7 -1
  80. package/scripts/lib/file-lock.mjs +151 -8
  81. package/scripts/lib/git-porcelain.mjs +113 -0
  82. package/scripts/lib/instruction-budget-guard.mjs +415 -47
  83. package/scripts/lib/io.mjs +29 -4
  84. package/scripts/lib/issue-budget-reconcile.mjs +392 -0
  85. package/scripts/lib/issue-budget.mjs +412 -9
  86. package/scripts/lib/learnings/evolve-telemetry.mjs +1 -2
  87. package/scripts/lib/learnings/sizing-subject.mjs +44 -0
  88. package/scripts/lib/locks/staging-fence-lock.mjs +19 -38
  89. package/scripts/lib/locks/state-md-lock.mjs +19 -41
  90. package/scripts/lib/maintenance-due-banner.mjs +450 -0
  91. package/scripts/lib/owner-config.example.yaml +29 -46
  92. package/scripts/lib/owner-yaml.mjs +14 -13
  93. package/scripts/lib/peer-cards/merger.mjs +143 -0
  94. package/scripts/lib/pre-dispatch-check.mjs +20 -14
  95. package/scripts/lib/project-hygiene.mjs +81 -30
  96. package/scripts/lib/quality-gate.mjs +27 -71
  97. package/scripts/lib/reconcile/engine.mjs +19 -1
  98. package/scripts/lib/reconcile/writer.mjs +278 -11
  99. package/scripts/lib/resource-probe/evaluate.mjs +19 -21
  100. package/scripts/lib/rules-sync.mjs +34 -4
  101. package/scripts/lib/scope-echo.mjs +346 -0
  102. package/scripts/lib/session-close-backfill.mjs +182 -40
  103. package/scripts/lib/session-end/phase-skip.mjs +85 -86
  104. package/scripts/lib/session-end/tail-runner.mjs +178 -0
  105. package/scripts/lib/session-lock.mjs +62 -2
  106. package/scripts/lib/session-record-repair.mjs +91 -0
  107. package/scripts/lib/session-schema/constants.mjs +6 -0
  108. package/scripts/lib/session-schema/filters.mjs +26 -1
  109. package/scripts/lib/session-schema/validator.mjs +20 -0
  110. package/scripts/lib/session-shape.mjs +558 -0
  111. package/scripts/lib/session-start-probes.mjs +429 -56
  112. package/scripts/lib/session-token-rollup.mjs +95 -10
  113. package/scripts/lib/state-md/frontmatter-mutators.mjs +22 -34
  114. package/scripts/lib/state-md.mjs +1 -0
  115. package/scripts/lib/subagents-schema.mjs +77 -9
  116. package/scripts/lib/telemetry/pricing.mjs +197 -0
  117. package/scripts/lib/telemetry/sync.mjs +50 -1
  118. package/scripts/lib/test-runner/artifact-paths.mjs +30 -5
  119. package/scripts/lib/test-runner/issue-reconcile.mjs +45 -8
  120. package/scripts/lib/tmux-layout/layouts.mjs +62 -4
  121. package/scripts/lib/ux-grill/collect.mjs +1163 -0
  122. package/scripts/lib/ux-grill/compare.mjs +285 -0
  123. package/scripts/lib/ux-grill/manifest.mjs +618 -0
  124. package/scripts/lib/ux-grill/measures.mjs +431 -0
  125. package/scripts/lib/ux-grill/paths.mjs +224 -0
  126. package/scripts/lib/ux-grill/pencil-coverage.mjs +284 -0
  127. package/scripts/lib/ux-grill/reconcile.mjs +344 -0
  128. package/scripts/lib/ux-grill/run-record.mjs +316 -0
  129. package/scripts/lib/ux-grill/schema.mjs +321 -0
  130. package/scripts/lib/validate/check-skill-script-paths.mjs +33 -10
  131. package/scripts/lib/validate/check-untracked-test-deps.mjs +33 -19
  132. package/scripts/lib/validate/check-unwired-features.mjs +56 -27
  133. package/scripts/lib/vault-mirror/process.mjs +2 -1
  134. package/scripts/lib/vault-status/board-lock.mjs +18 -0
  135. package/scripts/lib/vault-status/board-writer.mjs +8 -0
  136. package/scripts/lib/vault-status/narrative-mirror.mjs +4 -4
  137. package/scripts/lib/wave-resource-gate.mjs +23 -27
  138. package/scripts/lib/wave-sizing.mjs +10 -3
  139. package/scripts/materialize-wave-scope.mjs +68 -14
  140. package/scripts/mcp-server.sh +16 -1
  141. package/scripts/print-applicable-rules.mjs +7 -6
  142. package/scripts/print-learnings-index.mjs +3 -2
  143. package/scripts/release.mjs +7 -2
  144. package/scripts/session-shape.mjs +266 -0
  145. package/skills/_shared/config-reading.md +15 -9
  146. package/skills/_shared/private-capability-context.md +89 -0
  147. package/skills/bootstrap/SKILL.md +60 -209
  148. package/skills/bootstrap/_shared-template.md +99 -14
  149. package/skills/bootstrap/deep-template.md +36 -26
  150. package/skills/bootstrap/fast-template.md +44 -8
  151. package/skills/bootstrap/intensity-heuristic.md +10 -4
  152. package/skills/bootstrap/private-contract.md +119 -0
  153. package/skills/bootstrap/public-fallback.md +30 -18
  154. package/skills/bootstrap/references/bootstrap-ecosystem-health-flow.md +48 -0
  155. package/skills/bootstrap/references/bootstrap-refresh-lock-flow.md +37 -0
  156. package/skills/bootstrap/references/bootstrap-retroactive-flow.md +108 -0
  157. package/skills/bootstrap/references/bootstrap-rules-fetch-bridge.md +64 -0
  158. package/skills/bootstrap/standard-template.md +39 -24
  159. package/skills/claude-md-drift-check/SKILL.md +9 -2
  160. package/skills/claude-md-drift-check/checker.mjs +213 -21
  161. package/skills/discovery/SKILL.md +6 -173
  162. package/skills/discovery/probes/vault-staleness.mjs +35 -5
  163. package/skills/discovery/probes-docs.md +8 -4
  164. package/skills/discovery/probes-supply-chain.md +4 -2
  165. package/skills/discovery/probes-ui.md +8 -4
  166. package/skills/discovery/probes-vault.md +12 -4
  167. package/skills/discovery/references/discovery-interactive-triage.md +139 -0
  168. package/skills/discovery/references/discovery-triage-state.md +54 -0
  169. package/skills/docs-orchestrator/audience-mapping.md +1 -1
  170. package/skills/eval/rubric-v1.md +13 -0
  171. package/skills/evolve/SKILL.md +2 -458
  172. package/skills/evolve/references/evolve-analyze-mode.md +360 -0
  173. package/skills/evolve/references/evolve-dialectic-mode.md +139 -0
  174. package/skills/gitlab-ops/SKILL.md +3 -3
  175. package/skills/grill/SKILL.md +1 -1
  176. package/skills/memory-cleanup/SKILL.md +2 -2
  177. package/skills/plan/mode-new.md +9 -0
  178. package/skills/plan/mode-retro.md +4 -3
  179. package/skills/reconcile/SKILL.md +11 -1
  180. package/skills/session-end/SKILL.md +3 -2
  181. package/skills/session-end/drift-operations.md +20 -5
  182. package/skills/session-end/metrics-collection.md +1 -0
  183. package/skills/session-end/phase-3-2-docs-verification.md +1 -1
  184. package/skills/session-end/phase-3-6-tail.md +27 -67
  185. package/skills/session-end/phase-3-7a-recommendations.md +2 -2
  186. package/skills/session-end/references/phase-2-quality-gate.md +3 -3
  187. package/skills/session-end/references/phase-3-documentation-updates.md +8 -6
  188. package/skills/session-end/references/phase-5-issue-cleanup.md +32 -1
  189. package/skills/session-end/session-metrics-write.md +33 -12
  190. package/skills/session-plan/SKILL.md +46 -180
  191. package/skills/session-plan/references/session-plan-task-classification.md +152 -0
  192. package/skills/session-plan/wave-template.md +8 -15
  193. package/skills/session-start/SKILL.md +41 -7
  194. package/skills/session-start/phase-2-5-docs-planning.md +1 -1
  195. package/skills/session-start/phase-8-5-express-path.md +12 -9
  196. package/skills/session-start/references/operations-contract.md +114 -0
  197. package/skills/session-start/references/phase-1-5-session-continuity.md +2 -0
  198. package/skills/session-start/references/phase-4-ssot-environment-check.md +42 -24
  199. package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +3 -1
  200. package/skills/session-start/soul.md +2 -2
  201. package/skills/test-runner/SKILL.md +1 -1
  202. package/skills/test-runner/rubric-v1.md +2 -2
  203. package/skills/tmux-layout/SKILL.md +3 -1
  204. package/skills/ux-grill/SKILL.md +211 -0
  205. package/skills/ux-grill/rubric-v2.md +201 -0
  206. package/skills/ux-grill/soul.md +76 -0
  207. package/skills/wave-executor/SKILL.md +32 -127
  208. package/skills/wave-executor/circuit-breaker.md +3 -1
  209. package/skills/wave-executor/references/wave-executor-quality-gate.md +61 -0
  210. package/skills/wave-executor/references/wave-executor-state-init.md +86 -0
  211. package/skills/wave-executor/references/wave-loop-dispatch.md +12 -2
  212. package/skills/wave-executor/references/wave-loop-review.md +19 -6
  213. package/skills/wave-executor/references/wave-loop-scope-manifest.md +6 -2
  214. package/templates/_shared/ux-manifest.template.md +149 -0
  215. package/templates/nextjs-minimal/package.json +1 -1
  216. package/templates/node-minimal/package.json +1 -1
  217. package/scripts/lib/multi-provider-build/providers.mjs +0 -64
  218. package/scripts/lib/multi-provider-build/templating.mjs +0 -130
  219. package/scripts/lib/owner-config/coerce.mjs +0 -29
  220. package/scripts/lib/owner-config/constants.mjs +0 -21
  221. package/scripts/lib/owner-config/defaults.mjs +0 -50
  222. package/scripts/lib/owner-config/error.mjs +0 -19
  223. package/scripts/lib/owner-config/index.mjs +0 -13
  224. package/scripts/lib/owner-config/merge.mjs +0 -52
  225. package/scripts/lib/owner-config/validate.mjs +0 -259
  226. package/scripts/lib/owner-config-loader.mjs +0 -170
  227. package/scripts/lib/owner-config.mjs +0 -28
  228. package/scripts/lib/soul-resolve.mjs +0 -130
  229. package/scripts/lib/vault-mirror/render.mjs +0 -8
  230. package/templates/_shared/journey-manifest.md +0 -114
@@ -0,0 +1,149 @@
1
+ <!-- source: session-orchestrator plugin (canonical: templates/_shared/ux-manifest.template.md) -->
2
+ ---
3
+ # ux-grill manifest — copy to `.orchestrator/ux-manifest.md` in the target repo.
4
+ #
5
+ # Every value below is a PLACEHOLDER. Replace them; never paste a credential,
6
+ # a token or a production host into this file — it is tracked by git.
7
+
8
+ # REQUIRED. Must be loopback (127.0.0.1 / localhost / [::1]). A non-loopback
9
+ # base-url aborts the mechanical run with "base-url must be loopback".
10
+ base-url: http://127.0.0.1:3100
11
+
12
+ # REQUIRED. `dev` or `prod`. A dev build is not a geometry measurement basis,
13
+ # so target-size findings from it are marked `provisional: true`.
14
+ build: dev
15
+
16
+ # Filename (relative to the target repo root) of the GITIGNORED env file that
17
+ # holds the VALUES for every env NAME named below. Omit it if no run needs
18
+ # credentials or guarded endpoints.
19
+ env-file: .env.e2e.local
20
+
21
+ # Env NAMES whose values must point at loopback. Use this for every endpoint
22
+ # the app talks to (API base, database URL, storage) so a stray production
23
+ # value cannot be written to by a seed or a journey. The value is checked but
24
+ # never printed.
25
+ guarded-url-envs:
26
+ - APP_API_BASE_URL
27
+ - APP_DATABASE_URL
28
+
29
+ # Optional command that seeds deterministic test data before the run.
30
+ seed-command: npm run seed:e2e
31
+
32
+ # Evaluation personas. Credentials are env NAMES only — the values live in
33
+ # `env-file`. Use `personas: []` for an app that needs no login.
34
+ personas:
35
+ - name: solo-operator
36
+ login-env-email: LOGIN_EMAIL_SOLO
37
+ login-env-password: LOGIN_PASSWORD_SOLO
38
+ goal: Get from the dashboard to a finished document without help.
39
+ - name: accountant
40
+ login-env-email: LOGIN_EMAIL_ACCOUNTANT
41
+ login-env-password: LOGIN_PASSWORD_ACCOUNTANT
42
+ goal: Review a client's numbers and export them.
43
+
44
+ # Routes to measure. `title-pattern` is a regular expression matched against
45
+ # the page title. `frame` is optional and only used by the Pencil coverage
46
+ # step (`desktop` | `mobile` | `both` | `none`).
47
+ routes:
48
+ - path: /dashboard
49
+ title-pattern: ^Dashboard
50
+ persona: solo-operator
51
+ frame: both
52
+ - path: /documents/new
53
+ title-pattern: ^New document
54
+ persona: solo-operator
55
+
56
+ # Journeys are replayed verbatim: every entry of `steps` is one agent-browser
57
+ # command line, so the mechanical stage can count them without judging them.
58
+ #
59
+ # TRUST: each step line is split into an argv array and handed to the
60
+ # `agent-browser` binary (never to a shell) — but NOT verbatim: the first token
61
+ # must be one of the ALLOWLISTED UI verbs below, anything else aborts the run
62
+ # with `step-verb-not-allowed`.
63
+ #
64
+ # back check click dblclick drag fill find focus forward get
65
+ # hover is keyboard open press reload scroll scrollintoview
66
+ # select snapshot type uncheck wait
67
+ #
68
+ # The denied half of the CLI is what the allowlist exists for: `upload` and
69
+ # `cookies set --curl` read arbitrary HOST FILES into the page, `download` and
70
+ # `pdf` write arbitrary host paths, `eval` can exfiltrate off-origin, `connect`
71
+ # retargets a foreign browser, and `close --all` kills every other agent's
72
+ # session on the machine. So the trust model is "allowlisted UI verbs", not
73
+ # "anything the CLI accepts" — whoever can commit this file can drive the
74
+ # BROWSER, not the host.
75
+ #
76
+ # An `open` step must resolve to the same origin as `base-url`, else
77
+ # `step-open-off-origin`. A step may not carry `--session`: the run owns its
78
+ # session, and a step that retargets it aborts with `step-session-override`.
79
+ #
80
+ # `start` must resolve to the SAME ORIGIN as `base-url` (a path is the normal
81
+ # form). An off-origin `start` aborts with `journey-start-off-origin` — with a
82
+ # persona attached, an absolute foreign URL would type real credentials into a
83
+ # foreign page. The same rule holds for every `routes[].path`.
84
+ journeys:
85
+ - name: create-first-document
86
+ persona: solo-operator
87
+ start: /dashboard
88
+ steps:
89
+ - click "New document"
90
+ - type "#title" "Placeholder title"
91
+ - click "Save"
92
+ success: /documents/
93
+ max-steps: 6
94
+
95
+ # Viewports. Defaults to exactly these two when the key is absent.
96
+ #
97
+ # Every viewport is VERIFIED after it is applied: `window.innerWidth` must equal
98
+ # the expectation, else the viewport is skipped as `device-mismatch` rather than
99
+ # captured under a wrong label. The expectation is the `viewport:` width, the
100
+ # built-in width of a known `device:` name (iPhone 15/16 393, iPhone 16 Pro 402,
101
+ # iPhone 17 402, iPad 820, iPad Pro 1024, Pixel 9 412, Galaxy S25 360 — measured
102
+ # against agent-browser 0.37.1), or an explicit `expected-width:`. A `device:`
103
+ # outside that list NEEDS `expected-width:`, otherwise the viewport is skipped:
104
+ # an unknown device name leaves the previous device in place, so an unverified
105
+ # width is exactly how desktop captures end up labelled `mobile`.
106
+ viewports:
107
+ - name: desktop
108
+ viewport: 1440x900
109
+ - name: mobile
110
+ device: iPhone 15
111
+
112
+ # Optional design-coverage source.
113
+ pencil:
114
+ file: design/app.pen
115
+ ---
116
+
117
+ # UX Manifest — <REPO NAME>
118
+
119
+ > Per-repo UX truth: what a real user walks through, and what the mechanical
120
+ > stage is allowed to touch. Last verified: <YYYY-MM-DD> by <operator>
121
+
122
+ ## Notes
123
+
124
+ Free-form notes below the frontmatter. They are preserved verbatim and are not
125
+ parsed — use them for context the fields cannot carry.
126
+
127
+ **Credentials never go in this file.** The frontmatter names env VARIABLES
128
+ (`login-env-email`, `login-env-password`, `guarded-url-envs`); their values are
129
+ read at run time from the gitignored file named by `env-file`. A value pasted
130
+ here is a secret in a tracked file, and the run itself will never print one:
131
+ guarded-env and persona errors name the variable, never its content. `env-file`
132
+ must resolve INSIDE the repo (`env-file-outside-repo` otherwise) — it names a
133
+ file of this repo, never a host-wide secrets file.
134
+
135
+ **Run artefacts can carry a credential even though findings cannot.** Three
136
+ classes: journey step screenshots (one is taken after EVERY step, including the
137
+ one right after `fill #pw ${LOGIN_PASSWORD}`), the text of `errors --json`, and
138
+ the `html` snippets inside axe JSON. They all live under
139
+ `.orchestrator/metrics/ux-grill/`, which `/ux-grill` adds to this repo's
140
+ `.gitignore` on bootstrap — keep that line, and never commit a run directory.
141
+
142
+ ## Known exceptions
143
+
144
+ Findings that are known and deliberately accepted — with the reason and a
145
+ revisit trigger, so they are not re-filed every run.
146
+
147
+ | Finding | Why accepted | Revisit trigger |
148
+ |---------|--------------|-----------------|
149
+ | <…> | <…> | <…> |
@@ -22,6 +22,6 @@
22
22
  "eslint": "^9.0.0",
23
23
  "eslint-config-next": "^15.3.0",
24
24
  "typescript": "^5.7.0",
25
- "vitest": "^3.1.0"
25
+ "vitest": "^4.1.11"
26
26
  }
27
27
  }
@@ -16,6 +16,6 @@
16
16
  "eslint": "^9.0.0",
17
17
  "tsx": "^4.19.0",
18
18
  "typescript": "^5.7.0",
19
- "vitest": "^3.1.0"
19
+ "vitest": "^4.1.11"
20
20
  }
21
21
  }
@@ -1,64 +0,0 @@
1
- /**
2
- * scripts/lib/multi-provider-build/providers.mjs
3
- *
4
- * Provider configuration for the single-source → many-provider build PoC.
5
- *
6
- * Scope: the THREE harnesses session-orchestrator actually targets
7
- * (Claude Code primary, Codex CLI, Cursor IDE). Inspired by pbakaus/impeccable's
8
- * 12-provider build (Apache-2.0) — we deliberately keep the templating mechanic
9
- * and drop the 12-provider breadth (maintenance ≫ value for providers with ~0
10
- * users; see the linked backlog issue's overengineering verdict).
11
- *
12
- * Each provider entry:
13
- * {
14
- * key, // canonical id used in build output paths
15
- * tags, // <tag> block markers this provider keeps (rest are stripped)
16
- * configDir, // on-disk harness dir
17
- * placeholders: { model, configFile, commandPrefix, askInstruction }
18
- * }
19
- */
20
-
21
- /** @typedef {{key:string, tags:string[], configDir:string, placeholders:{model:string, configFile:string, commandPrefix:string, askInstruction:string}}} ProviderConfig */
22
-
23
- /** @type {Record<string, ProviderConfig>} */
24
- export const PROVIDERS = {
25
- 'claude-code': {
26
- key: 'claude-code',
27
- tags: ['claude-code', 'claude'],
28
- configDir: '.claude',
29
- placeholders: {
30
- model: 'Claude',
31
- configFile: 'CLAUDE.md',
32
- commandPrefix: '/',
33
- askInstruction: 'STOP and call the AskUserQuestion tool to clarify.',
34
- },
35
- },
36
- codex: {
37
- key: 'codex',
38
- tags: ['codex'],
39
- configDir: '.codex',
40
- placeholders: {
41
- model: 'GPT',
42
- configFile: 'AGENTS.md',
43
- commandPrefix: '$',
44
- askInstruction: "STOP and use Codex's structured user-input tool to clarify.",
45
- },
46
- },
47
- cursor: {
48
- key: 'cursor',
49
- tags: ['cursor'],
50
- configDir: '.cursor',
51
- placeholders: {
52
- model: 'the model',
53
- configFile: '.cursorrules',
54
- commandPrefix: '/',
55
- askInstruction: 'ask the user directly to clarify before proceeding.',
56
- },
57
- },
58
- };
59
-
60
- /** The full set of recognized provider block tags across ALL providers. */
61
- export const ALL_BLOCK_TAGS = new Set(Object.values(PROVIDERS).flatMap((p) => p.tags));
62
-
63
- /** Provider keys in a stable order. */
64
- export const PROVIDER_KEYS = Object.keys(PROVIDERS);
@@ -1,130 +0,0 @@
1
- /**
2
- * scripts/lib/multi-provider-build/templating.mjs
3
- *
4
- * Single-source → many-provider templating (PoC). One source document with
5
- * provider-conditional blocks + placeholders renders to a provider-specific
6
- * artifact. This replaces the "maintain N near-identical copies by hand" model
7
- * our codex-install.mjs / cursor-install.mjs currently imply.
8
- *
9
- * Two primitives (clean-room reimplementation of impeccable's mechanic):
10
- * 1. compileProviderBlocks(content, activeTags)
11
- * <codex> … </codex> blocks: keep body iff the tag is active, else drop.
12
- * Unknown tags are left untouched (so real HTML/JSX isn't mangled).
13
- * 2. replacePlaceholders(content, providerKey, opts)
14
- * {{model}} {{config_file}} {{command_prefix}} {{ask_instruction}}
15
- * {{available_commands}} substitution + optional /cmd → $cmd rewrite.
16
- *
17
- * renderForProvider() composes both in the correct order (blocks first, then
18
- * placeholders — matching impeccable's pipeline).
19
- */
20
-
21
- import { PROVIDERS, ALL_BLOCK_TAGS } from './providers.mjs';
22
-
23
- /**
24
- * Keep `<tag>…</tag>` blocks whose tag is in `activeTags`; strip the rest.
25
- * Only tags in `knownTags` are treated as conditional blocks — any other
26
- * `<foo>…</foo>` (e.g. real markup) is left exactly as-is.
27
- *
28
- * Tags must sit on their own line, e.g.:
29
- * <codex>
30
- * Codex-only text.
31
- * </codex>
32
- *
33
- * @param {string} content
34
- * @param {Iterable<string>} activeTags
35
- * @param {Iterable<string>} [knownTags] — defaults to ALL_BLOCK_TAGS
36
- * @returns {string}
37
- */
38
- export function compileProviderBlocks(content, activeTags, knownTags = ALL_BLOCK_TAGS) {
39
- const active = new Set(activeTags);
40
- const known = new Set(knownTags);
41
- let compiledAny = false;
42
-
43
- // Match a block: leading newline (or start), <tag> on its own line, body,
44
- // </tag> on its own line. Non-greedy body; the backreference enforces matching.
45
- const blockRe = /(^|\r?\n)[ \t]*<([a-z][a-z0-9-]*)>[ \t]*\r?\n([\s\S]*?)\r?\n[ \t]*<\/\2>[ \t]*(?=\r?\n|$)/g;
46
-
47
- const out = content.replace(blockRe, (match, prefix, tag, body) => {
48
- if (!known.has(tag)) return match; // not a provider block — leave untouched
49
- compiledAny = true;
50
- return active.has(tag) ? `${prefix}${body}` : prefix;
51
- });
52
-
53
- // Collapse the 3+ blank-line runs that stripping can leave behind.
54
- return compiledAny ? out.replace(/(\r?\n){3,}/g, '\n\n') : out;
55
- }
56
-
57
- /**
58
- * Substitute {{placeholders}} for a provider and (optionally) rewrite command
59
- * invocations from `/name` to the provider's prefix.
60
- *
61
- * @param {string} content
62
- * @param {string} providerKey
63
- * @param {{ commandNames?: string[] }} [opts]
64
- * @returns {string}
65
- */
66
- export function replacePlaceholders(content, providerKey, opts = {}) {
67
- const provider = PROVIDERS[providerKey];
68
- if (!provider) throw new Error(`Unknown provider: ${providerKey}`);
69
- const { model, configFile, commandPrefix, askInstruction } = provider.placeholders;
70
- const commandNames = opts.commandNames ?? [];
71
-
72
- const availableCommands = commandNames.map((n) => `${commandPrefix}${n}`).join(', ');
73
-
74
- let result = content
75
- .replace(/\{\{model\}\}/g, model)
76
- .replace(/\{\{config_file\}\}/g, configFile)
77
- .replace(/\{\{ask_instruction\}\}/g, askInstruction)
78
- .replace(/\{\{available_commands\}\}/g, availableCommands)
79
- .replace(/\{\{command_prefix\}\}/g, commandPrefix);
80
-
81
- // Rewrite `/cmd` invocations to the provider prefix (e.g. Codex uses `$`).
82
- // Longest names first so `/session-end` is rewritten before `/session`.
83
- if (commandPrefix !== '/' && commandNames.length > 0) {
84
- const sorted = [...commandNames].sort((a, b) => b.length - a.length);
85
- for (const name of sorted) {
86
- // `/name` where name is followed by a non-identifier char or end.
87
- const re = new RegExp(`\\/(${escapeRegex(name)})(?=[^a-zA-Z0-9_-]|$)`, 'g');
88
- // Use a replacement FUNCTION, not a string — a `$` commandPrefix would
89
- // otherwise collide with String.replace's `$$`/`$1` escape syntax.
90
- result = result.replace(re, (_match, g1) => `${commandPrefix}${g1}`);
91
- }
92
- }
93
-
94
- return result;
95
- }
96
-
97
- /**
98
- * Render a source document for a single provider: compile blocks, then
99
- * substitute placeholders.
100
- *
101
- * @param {string} source
102
- * @param {string} providerKey
103
- * @param {{ commandNames?: string[] }} [opts]
104
- * @returns {string}
105
- */
106
- export function renderForProvider(source, providerKey, opts = {}) {
107
- const provider = PROVIDERS[providerKey];
108
- if (!provider) throw new Error(`Unknown provider: ${providerKey}`);
109
- const compiled = compileProviderBlocks(source, provider.tags);
110
- return replacePlaceholders(compiled, providerKey, opts);
111
- }
112
-
113
- /**
114
- * Render a source for every configured provider.
115
- * @param {string} source
116
- * @param {{ commandNames?: string[] }} [opts]
117
- * @returns {Record<string,string>} keyed by provider key
118
- */
119
- export function renderAll(source, opts = {}) {
120
- const out = {};
121
- for (const key of Object.keys(PROVIDERS)) {
122
- out[key] = renderForProvider(source, key, opts);
123
- }
124
- return out;
125
- }
126
-
127
- /** @param {string} s */
128
- function escapeRegex(s) {
129
- return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
130
- }
@@ -1,29 +0,0 @@
1
- /**
2
- * owner-config/coerce.mjs — Strict-mode wrapper around validate().
3
- *
4
- * Imports: validate + OwnerConfigError. Throws on validation failure, returns
5
- * the normalized value on success. Convenient for CLI entrypoints and tests
6
- * that want to assert on success without inspecting `ok`.
7
- */
8
-
9
- import { validate } from './validate.mjs';
10
- import { OwnerConfigError } from './error.mjs';
11
-
12
- /**
13
- * Strict-mode wrapper around validate(). Returns the normalized value when
14
- * the input passes the gate, otherwise throws OwnerConfigError with the
15
- * full error list attached as `.errors`.
16
- *
17
- * @param {unknown} raw
18
- * @returns {object}
19
- */
20
- export function coerce(raw) {
21
- const result = validate(raw);
22
- if (!result.ok) {
23
- throw new OwnerConfigError(
24
- `owner config validation failed (${result.errors.length} error${result.errors.length === 1 ? '' : 's'})`,
25
- result.errors
26
- );
27
- }
28
- return result.value;
29
- }
@@ -1,21 +0,0 @@
1
- /**
2
- * owner-config/constants.mjs — Schema constants for the owner persona config.
3
- *
4
- * Leaf module. No imports. All exported arrays are frozen so consumers cannot
5
- * accidentally mutate the canonical enum sets.
6
- */
7
-
8
- /** Current owner-config schema version. New writes are stamped with this. */
9
- export const CURRENT_OWNER_SCHEMA_VERSION = 1;
10
-
11
- /** Valid values for tone.style. */
12
- export const VALID_TONE_STYLES = Object.freeze(['direct', 'neutral', 'friendly']);
13
-
14
- /** Valid values for efficiency.output-level. */
15
- export const VALID_OUTPUT_LEVELS = Object.freeze(['lite', 'full', 'ultra']);
16
-
17
- /** Valid values for efficiency.preamble. */
18
- export const VALID_PREAMBLE_LEVELS = Object.freeze(['minimal', 'verbose']);
19
-
20
- /** Valid values for efficiency.comments-in-code. */
21
- export const VALID_COMMENTS_LEVELS = Object.freeze(['minimal', 'full']);
@@ -1,50 +0,0 @@
1
- /**
2
- * owner-config/defaults.mjs — Canonical default-filled owner config factory.
3
- *
4
- * Imports constants only. Leaf in the dependency sense (no circular deps).
5
- * Returns the fully-default-filled config with `owner.name` and
6
- * `owner.language` intentionally left blank — those are required from the user;
7
- * the bootstrap interview (D2) fills them in.
8
- */
9
-
10
- import { CURRENT_OWNER_SCHEMA_VERSION } from './constants.mjs';
11
-
12
- /**
13
- * Returns the canonical default-filled owner config. `owner.name` and
14
- * `owner.language` are intentionally empty — those are user-required fields
15
- * the bootstrap interview (D2) fills in. The `defaults()` shape is what
16
- * `merge()` uses as its base when callers pass partial overrides.
17
- *
18
- * @returns {object}
19
- */
20
- export function defaults() {
21
- return {
22
- 'schema-version': CURRENT_OWNER_SCHEMA_VERSION,
23
- owner: {
24
- name: '',
25
- 'email-hash': null,
26
- language: '',
27
- },
28
- tone: {
29
- style: 'neutral',
30
- tonality: null,
31
- },
32
- efficiency: {
33
- 'output-level': 'full',
34
- preamble: 'minimal',
35
- 'comments-in-code': 'minimal',
36
- },
37
- 'hardware-sharing': {
38
- enabled: false,
39
- 'hash-salt': null,
40
- },
41
- defaults: {
42
- 'preferred-test-command': null,
43
- 'preferred-editor': null,
44
- },
45
- metadata: {
46
- created_at: null,
47
- updated_at: null,
48
- },
49
- };
50
- }
@@ -1,19 +0,0 @@
1
- /**
2
- * owner-config/error.mjs — OwnerConfigError class.
3
- *
4
- * Leaf module. No imports. Thrown by coerce() when validation fails. The
5
- * `.errors` array carries the full list of human-readable failure messages
6
- * collected by validate().
7
- */
8
-
9
- export class OwnerConfigError extends Error {
10
- /**
11
- * @param {string} message
12
- * @param {string[]} [errors]
13
- */
14
- constructor(message, errors = []) {
15
- super(message);
16
- this.name = 'OwnerConfigError';
17
- this.errors = errors;
18
- }
19
- }
@@ -1,13 +0,0 @@
1
- /**
2
- * owner-config/index.mjs — Re-export hub for all 10 public symbols.
3
- *
4
- * DAG: constants/error (leaves) → defaults → merge; constants/error → validate → coerce.
5
- * This hub is the single import target for `scripts/lib/owner-config.mjs` (top wrapper).
6
- */
7
-
8
- export { CURRENT_OWNER_SCHEMA_VERSION, VALID_TONE_STYLES, VALID_OUTPUT_LEVELS, VALID_PREAMBLE_LEVELS, VALID_COMMENTS_LEVELS } from './constants.mjs';
9
- export { OwnerConfigError } from './error.mjs';
10
- export { defaults } from './defaults.mjs';
11
- export { validate } from './validate.mjs';
12
- export { coerce } from './coerce.mjs';
13
- export { merge } from './merge.mjs';
@@ -1,52 +0,0 @@
1
- /**
2
- * owner-config/merge.mjs — Deep merge for owner persona configs.
3
- *
4
- * Imports: constants + defaults. One-level-deep section merge because the
5
- * schema has no nested object leaves beyond top-level sections. The output is
6
- * always a full default-filled config — either input may be partial.
7
- */
8
-
9
- import { CURRENT_OWNER_SCHEMA_VERSION } from './constants.mjs';
10
- import { defaults } from './defaults.mjs';
11
-
12
- function isPlainObject(v) {
13
- return v !== null && typeof v === 'object' && !Array.isArray(v);
14
- }
15
-
16
- /**
17
- * Deep merge two owner configs. `override` values win on every leaf key
18
- * where they are defined (i.e. not undefined). The returned value is a
19
- * full default-filled config — either input may be partial.
20
- *
21
- * Used by D3 (`soul.md` runtime-merge) and D4 (baseline-propagation +
22
- * per-session override). The merge is one level deep on each top-level
23
- * section (owner, tone, efficiency, hardware-sharing, defaults, metadata)
24
- * because the schema has no nested object leaves beyond that.
25
- *
26
- * @param {object|null|undefined} base
27
- * @param {object|null|undefined} override
28
- * @returns {object}
29
- */
30
- export function merge(base, override) {
31
- const baseSafe = isPlainObject(base) ? base : {};
32
- const overSafe = isPlainObject(override) ? override : {};
33
- const def = defaults();
34
-
35
- const sections = ['owner', 'tone', 'efficiency', 'hardware-sharing', 'defaults', 'metadata'];
36
- const out = {
37
- 'schema-version': CURRENT_OWNER_SCHEMA_VERSION,
38
- };
39
-
40
- for (const section of sections) {
41
- const baseSection = isPlainObject(baseSafe[section]) ? baseSafe[section] : {};
42
- const overSection = isPlainObject(overSafe[section]) ? overSafe[section] : {};
43
- out[section] = { ...def[section], ...baseSection };
44
- for (const [k, v] of Object.entries(overSection)) {
45
- if (v !== undefined) {
46
- out[section][k] = v;
47
- }
48
- }
49
- }
50
-
51
- return out;
52
- }