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.
- package/.agents/skills/session-plan/SKILL.md +1 -1
- package/.agents/skills/session-start/SKILL.md +1 -1
- package/.agents/skills/ux-grill/SKILL.md +22 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +3 -2
- package/.codex-plugin/plugin.json +1 -1
- package/.codex-plugin/skills/session-plan/SKILL.md +1 -1
- package/.codex-plugin/skills/session-start/SKILL.md +1 -1
- package/.codex-plugin/skills/ux-grill/SKILL.md +21 -0
- package/.codex-plugin/skills/ux-grill/agents/openai.yaml +5 -0
- package/.cursor/commands/ux-grill.md +14 -0
- package/.cursor/skills/session-plan/SKILL.md +1 -1
- package/.cursor/skills/session-start/SKILL.md +1 -1
- package/.cursor/skills/ux-grill/SKILL.md +13 -0
- package/.cursor-plugin/plugin.json +1 -1
- package/AGENTS.md +2 -1
- package/CHANGELOG.md +128 -1
- package/README.md +98 -86
- package/agents/dialectic-deriver.md +11 -0
- package/agents/ux-evaluator.md +1 -1
- package/commands/close.md +3 -3
- package/commands/go.md +2 -0
- package/commands/memory-cleanup.md +4 -3
- package/commands/persona-panel.md +1 -1
- package/commands/session.md +3 -2
- package/commands/ux-grill.md +51 -0
- package/docs/README.md +4 -4
- package/docs/USER-GUIDE.md +117 -50
- package/docs/agent-authoring.md +2 -2
- package/docs/baseline.md +55 -1
- package/docs/ci-setup.md +1 -1
- package/docs/codex-setup.md +9 -0
- package/docs/components.md +9 -9
- package/docs/cursor-setup.md +1 -0
- package/docs/events-schema.md +13 -6
- package/docs/github-mirror-protection.md +61 -20
- package/docs/instruction-delivery.md +1 -1
- package/docs/memory-proposal-flow.md +3 -3
- package/docs/migration-v4.md +2 -2
- package/docs/migration-v5.md +62 -0
- package/docs/owner-config-schema.md +74 -90
- package/docs/persona-panel.md +4 -4
- package/docs/pi-setup.md +1 -0
- package/docs/rule-authoring.md +13 -6
- package/docs/scope-collision-guard.md +16 -0
- package/docs/session-config-reference.md +55 -22
- package/docs/session-config-template.md +9 -5
- package/docs/vault-docs-architecture.md +4 -2
- package/hooks/_lib/hook-import-set.json +70 -3
- package/hooks/_lib/lock-bootstrap.mjs +84 -1
- package/hooks/_lib/vcs-create-matcher.mjs +401 -16
- package/hooks/enforce-scope.mjs +201 -0
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +5 -0
- package/hooks/hooks.json +7 -2
- package/hooks/on-session-start.mjs +171 -49
- package/hooks/post-bash-issue-budget-refund.mjs +375 -0
- package/hooks/pre-auq-clarity.mjs +70 -18
- package/hooks/pre-bash-issue-budget.mjs +170 -26
- package/hooks/subagent-telemetry.mjs +106 -20
- package/package.json +5 -4
- package/pi/prompts/ux-grill.md +12 -0
- package/scripts/baseline-archetypes.mjs +28 -0
- package/scripts/ci/assert-vitest-green.mjs +4 -2
- package/scripts/dialectic-deriver.mjs +32 -8
- package/scripts/emit-session.mjs +72 -1
- package/scripts/lib/agent-status.mjs +441 -9
- package/scripts/lib/auq/schema.mjs +10 -3
- package/scripts/lib/auto-dialectic.mjs +0 -68
- package/scripts/lib/baseline-archetypes.mjs +439 -0
- package/scripts/lib/build-live-signals.mjs +5 -6
- package/scripts/lib/ci-status-banner.mjs +29 -6
- package/scripts/lib/claude-md-budget-lint.mjs +52 -2
- package/scripts/lib/config/issue-budget.mjs +68 -8
- package/scripts/lib/config/private-config-dir.mjs +3 -2
- package/scripts/lib/config/remote-hosts.mjs +2 -2
- package/scripts/lib/config-schema.mjs +79 -0
- package/scripts/lib/config.mjs +12 -1
- package/scripts/lib/eval/engine.mjs +7 -1
- package/scripts/lib/file-lock.mjs +151 -8
- package/scripts/lib/git-porcelain.mjs +113 -0
- package/scripts/lib/instruction-budget-guard.mjs +415 -47
- package/scripts/lib/io.mjs +29 -4
- package/scripts/lib/issue-budget-reconcile.mjs +392 -0
- package/scripts/lib/issue-budget.mjs +412 -9
- package/scripts/lib/learnings/evolve-telemetry.mjs +1 -2
- package/scripts/lib/learnings/sizing-subject.mjs +44 -0
- package/scripts/lib/locks/staging-fence-lock.mjs +19 -38
- package/scripts/lib/locks/state-md-lock.mjs +19 -41
- package/scripts/lib/maintenance-due-banner.mjs +450 -0
- package/scripts/lib/owner-config.example.yaml +29 -46
- package/scripts/lib/owner-yaml.mjs +14 -13
- package/scripts/lib/peer-cards/merger.mjs +143 -0
- package/scripts/lib/pre-dispatch-check.mjs +20 -14
- package/scripts/lib/project-hygiene.mjs +81 -30
- package/scripts/lib/quality-gate.mjs +27 -71
- package/scripts/lib/reconcile/engine.mjs +19 -1
- package/scripts/lib/reconcile/writer.mjs +278 -11
- package/scripts/lib/resource-probe/evaluate.mjs +19 -21
- package/scripts/lib/rules-sync.mjs +34 -4
- package/scripts/lib/scope-echo.mjs +346 -0
- package/scripts/lib/session-close-backfill.mjs +182 -40
- package/scripts/lib/session-end/phase-skip.mjs +85 -86
- package/scripts/lib/session-end/tail-runner.mjs +178 -0
- package/scripts/lib/session-lock.mjs +62 -2
- package/scripts/lib/session-record-repair.mjs +91 -0
- package/scripts/lib/session-schema/constants.mjs +6 -0
- package/scripts/lib/session-schema/filters.mjs +26 -1
- package/scripts/lib/session-schema/validator.mjs +20 -0
- package/scripts/lib/session-shape.mjs +558 -0
- package/scripts/lib/session-start-probes.mjs +429 -56
- package/scripts/lib/session-token-rollup.mjs +95 -10
- package/scripts/lib/state-md/frontmatter-mutators.mjs +22 -34
- package/scripts/lib/state-md.mjs +1 -0
- package/scripts/lib/subagents-schema.mjs +77 -9
- package/scripts/lib/telemetry/pricing.mjs +197 -0
- package/scripts/lib/telemetry/sync.mjs +50 -1
- package/scripts/lib/test-runner/artifact-paths.mjs +30 -5
- package/scripts/lib/test-runner/issue-reconcile.mjs +45 -8
- package/scripts/lib/tmux-layout/layouts.mjs +62 -4
- package/scripts/lib/ux-grill/collect.mjs +1163 -0
- package/scripts/lib/ux-grill/compare.mjs +285 -0
- package/scripts/lib/ux-grill/manifest.mjs +618 -0
- package/scripts/lib/ux-grill/measures.mjs +431 -0
- package/scripts/lib/ux-grill/paths.mjs +224 -0
- package/scripts/lib/ux-grill/pencil-coverage.mjs +284 -0
- package/scripts/lib/ux-grill/reconcile.mjs +344 -0
- package/scripts/lib/ux-grill/run-record.mjs +316 -0
- package/scripts/lib/ux-grill/schema.mjs +321 -0
- package/scripts/lib/validate/check-skill-script-paths.mjs +33 -10
- package/scripts/lib/validate/check-untracked-test-deps.mjs +33 -19
- package/scripts/lib/validate/check-unwired-features.mjs +56 -27
- package/scripts/lib/vault-mirror/process.mjs +2 -1
- package/scripts/lib/vault-status/board-lock.mjs +18 -0
- package/scripts/lib/vault-status/board-writer.mjs +8 -0
- package/scripts/lib/vault-status/narrative-mirror.mjs +4 -4
- package/scripts/lib/wave-resource-gate.mjs +23 -27
- package/scripts/lib/wave-sizing.mjs +10 -3
- package/scripts/materialize-wave-scope.mjs +68 -14
- package/scripts/mcp-server.sh +16 -1
- package/scripts/print-applicable-rules.mjs +7 -6
- package/scripts/print-learnings-index.mjs +3 -2
- package/scripts/release.mjs +7 -2
- package/scripts/session-shape.mjs +266 -0
- package/skills/_shared/config-reading.md +15 -9
- package/skills/_shared/private-capability-context.md +89 -0
- package/skills/bootstrap/SKILL.md +60 -209
- package/skills/bootstrap/_shared-template.md +99 -14
- package/skills/bootstrap/deep-template.md +36 -26
- package/skills/bootstrap/fast-template.md +44 -8
- package/skills/bootstrap/intensity-heuristic.md +10 -4
- package/skills/bootstrap/private-contract.md +119 -0
- package/skills/bootstrap/public-fallback.md +30 -18
- package/skills/bootstrap/references/bootstrap-ecosystem-health-flow.md +48 -0
- package/skills/bootstrap/references/bootstrap-refresh-lock-flow.md +37 -0
- package/skills/bootstrap/references/bootstrap-retroactive-flow.md +108 -0
- package/skills/bootstrap/references/bootstrap-rules-fetch-bridge.md +64 -0
- package/skills/bootstrap/standard-template.md +39 -24
- package/skills/claude-md-drift-check/SKILL.md +9 -2
- package/skills/claude-md-drift-check/checker.mjs +213 -21
- package/skills/discovery/SKILL.md +6 -173
- package/skills/discovery/probes/vault-staleness.mjs +35 -5
- package/skills/discovery/probes-docs.md +8 -4
- package/skills/discovery/probes-supply-chain.md +4 -2
- package/skills/discovery/probes-ui.md +8 -4
- package/skills/discovery/probes-vault.md +12 -4
- package/skills/discovery/references/discovery-interactive-triage.md +139 -0
- package/skills/discovery/references/discovery-triage-state.md +54 -0
- package/skills/docs-orchestrator/audience-mapping.md +1 -1
- package/skills/eval/rubric-v1.md +13 -0
- package/skills/evolve/SKILL.md +2 -458
- package/skills/evolve/references/evolve-analyze-mode.md +360 -0
- package/skills/evolve/references/evolve-dialectic-mode.md +139 -0
- package/skills/gitlab-ops/SKILL.md +3 -3
- package/skills/grill/SKILL.md +1 -1
- package/skills/memory-cleanup/SKILL.md +2 -2
- package/skills/plan/mode-new.md +9 -0
- package/skills/plan/mode-retro.md +4 -3
- package/skills/reconcile/SKILL.md +11 -1
- package/skills/session-end/SKILL.md +3 -2
- package/skills/session-end/drift-operations.md +20 -5
- package/skills/session-end/metrics-collection.md +1 -0
- package/skills/session-end/phase-3-2-docs-verification.md +1 -1
- package/skills/session-end/phase-3-6-tail.md +27 -67
- package/skills/session-end/phase-3-7a-recommendations.md +2 -2
- package/skills/session-end/references/phase-2-quality-gate.md +3 -3
- package/skills/session-end/references/phase-3-documentation-updates.md +8 -6
- package/skills/session-end/references/phase-5-issue-cleanup.md +32 -1
- package/skills/session-end/session-metrics-write.md +33 -12
- package/skills/session-plan/SKILL.md +46 -180
- package/skills/session-plan/references/session-plan-task-classification.md +152 -0
- package/skills/session-plan/wave-template.md +8 -15
- package/skills/session-start/SKILL.md +41 -7
- package/skills/session-start/phase-2-5-docs-planning.md +1 -1
- package/skills/session-start/phase-8-5-express-path.md +12 -9
- package/skills/session-start/references/operations-contract.md +114 -0
- package/skills/session-start/references/phase-1-5-session-continuity.md +2 -0
- package/skills/session-start/references/phase-4-ssot-environment-check.md +42 -24
- package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +3 -1
- package/skills/session-start/soul.md +2 -2
- package/skills/test-runner/SKILL.md +1 -1
- package/skills/test-runner/rubric-v1.md +2 -2
- package/skills/tmux-layout/SKILL.md +3 -1
- package/skills/ux-grill/SKILL.md +211 -0
- package/skills/ux-grill/rubric-v2.md +201 -0
- package/skills/ux-grill/soul.md +76 -0
- package/skills/wave-executor/SKILL.md +32 -127
- package/skills/wave-executor/circuit-breaker.md +3 -1
- package/skills/wave-executor/references/wave-executor-quality-gate.md +61 -0
- package/skills/wave-executor/references/wave-executor-state-init.md +86 -0
- package/skills/wave-executor/references/wave-loop-dispatch.md +12 -2
- package/skills/wave-executor/references/wave-loop-review.md +19 -6
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +6 -2
- package/templates/_shared/ux-manifest.template.md +149 -0
- package/templates/nextjs-minimal/package.json +1 -1
- package/templates/node-minimal/package.json +1 -1
- package/scripts/lib/multi-provider-build/providers.mjs +0 -64
- package/scripts/lib/multi-provider-build/templating.mjs +0 -130
- package/scripts/lib/owner-config/coerce.mjs +0 -29
- package/scripts/lib/owner-config/constants.mjs +0 -21
- package/scripts/lib/owner-config/defaults.mjs +0 -50
- package/scripts/lib/owner-config/error.mjs +0 -19
- package/scripts/lib/owner-config/index.mjs +0 -13
- package/scripts/lib/owner-config/merge.mjs +0 -52
- package/scripts/lib/owner-config/validate.mjs +0 -259
- package/scripts/lib/owner-config-loader.mjs +0 -170
- package/scripts/lib/owner-config.mjs +0 -28
- package/scripts/lib/soul-resolve.mjs +0 -130
- package/scripts/lib/vault-mirror/render.mjs +0 -8
- 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
|
+
| <…> | <…> | <…> |
|
|
@@ -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
|
-
}
|