cohorte 2.10.1 → 3.0.0-dev.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/README.md +7 -0
- package/assets/manifest.json +193 -0
- package/assets/migrations/config/README.md +12 -0
- package/assets/migrations/state/0001_init.sql +154 -0
- package/assets/prompts/README.md +11 -0
- package/assets/prompts/agents/fixer.md +7 -0
- package/assets/prompts/agents/implementer.md +12 -0
- package/assets/prompts/agents/reviewer.md +8 -0
- package/assets/prompts/agents/security-reviewer.md +7 -0
- package/assets/prompts/phases/build.md +5 -0
- package/assets/prompts/phases/review.md +5 -0
- package/assets/prompts/phases/test.md +5 -0
- package/assets/prompts/system.md +11 -0
- package/assets/schemas/agent-output.schema.json +225 -0
- package/assets/schemas/auth-status.schema.json +87 -0
- package/assets/schemas/command-result.schema.json +192 -0
- package/assets/schemas/commands.schema.json +952 -0
- package/assets/schemas/config.schema.json +2259 -0
- package/assets/schemas/doctor-report.schema.json +104 -0
- package/assets/schemas/events.schema.json +8514 -0
- package/assets/schemas/fake-script.schema.json +549 -0
- package/assets/schemas/inspect.schema.json +3133 -0
- package/assets/schemas/manifest.schema.json +79 -0
- package/assets/schemas/ownership.schema.json +52 -0
- package/assets/schemas/policy-verdict.schema.json +282 -0
- package/assets/schemas/project-model.schema.json +783 -0
- package/assets/schemas/project-status.schema.json +217 -0
- package/assets/schemas/reconcile-plan.schema.json +155 -0
- package/assets/schemas/run-diff.schema.json +177 -0
- package/assets/schemas/run-snapshot-manifest.schema.json +2104 -0
- package/assets/schemas/run-state.schema.json +1755 -0
- package/assets/schemas/runtime-capabilities.schema.json +1545 -0
- package/assets/schemas/sandbox-capabilities.schema.json +116 -0
- package/assets/schemas/skill.schema.json +87 -0
- package/assets/schemas/spec.schema.json +202 -0
- package/assets/schemas/tool-catalogue.schema.json +655 -0
- package/assets/schemas/trust-record.schema.json +38 -0
- package/assets/skills/README.md +9 -0
- package/assets/skills/inspect-surface.md +4 -0
- package/dist/agent-host.mjs +50 -0
- package/dist/agent-host.mjs.map +1 -0
- package/dist/bundle-manifest.json +102 -0
- package/dist/chunks/__host-DgGPyco9.mjs +18 -0
- package/dist/chunks/__host-DgGPyco9.mjs.map +1 -0
- package/dist/chunks/approve-Cv135UqI.mjs +19 -0
- package/dist/chunks/approve-Cv135UqI.mjs.map +1 -0
- package/dist/chunks/auth-BT5oVp76.mjs +132 -0
- package/dist/chunks/auth-BT5oVp76.mjs.map +1 -0
- package/dist/chunks/auth-D8rCVY33.mjs +84 -0
- package/dist/chunks/auth-D8rCVY33.mjs.map +1 -0
- package/dist/chunks/brainstorm-CU10T1tm.mjs +7 -0
- package/dist/chunks/brainstorm-CU10T1tm.mjs.map +1 -0
- package/dist/chunks/cancel-KV_cpsqS.mjs +15 -0
- package/dist/chunks/cancel-KV_cpsqS.mjs.map +1 -0
- package/dist/chunks/canonical-C8UxeEmB.mjs +57 -0
- package/dist/chunks/canonical-C8UxeEmB.mjs.map +1 -0
- package/dist/chunks/catalogue-BvwSrM_j.mjs +1549 -0
- package/dist/chunks/catalogue-BvwSrM_j.mjs.map +1 -0
- package/dist/chunks/compose-Cr0hP0qm.mjs +7303 -0
- package/dist/chunks/compose-Cr0hP0qm.mjs.map +1 -0
- package/dist/chunks/config-DefHA2Lw.mjs +96 -0
- package/dist/chunks/config-DefHA2Lw.mjs.map +1 -0
- package/dist/chunks/contract-B0ra2-bm.mjs +295 -0
- package/dist/chunks/contract-B0ra2-bm.mjs.map +1 -0
- package/dist/chunks/contract-vYbwYr0Y.mjs +123 -0
- package/dist/chunks/contract-vYbwYr0Y.mjs.map +1 -0
- package/dist/chunks/deny-ClTxSvHJ.mjs +15 -0
- package/dist/chunks/deny-ClTxSvHJ.mjs.map +1 -0
- package/dist/chunks/diff-Qw0II0sj.mjs +101 -0
- package/dist/chunks/diff-Qw0II0sj.mjs.map +1 -0
- package/dist/chunks/discover-30Ciya_0.mjs +20 -0
- package/dist/chunks/discover-30Ciya_0.mjs.map +1 -0
- package/dist/chunks/doctor-DfCrKpfT.mjs +222 -0
- package/dist/chunks/doctor-DfCrKpfT.mjs.map +1 -0
- package/dist/chunks/fix-D4Tj8RVA.mjs +18 -0
- package/dist/chunks/fix-D4Tj8RVA.mjs.map +1 -0
- package/dist/chunks/gc-BBXlBF5w.mjs +161 -0
- package/dist/chunks/gc-BBXlBF5w.mjs.map +1 -0
- package/dist/chunks/init-Okz4mlhW.mjs +30 -0
- package/dist/chunks/init-Okz4mlhW.mjs.map +1 -0
- package/dist/chunks/inspect-Buby4cQD.mjs +22 -0
- package/dist/chunks/inspect-Buby4cQD.mjs.map +1 -0
- package/dist/chunks/logs-Giq5sUau.mjs +25 -0
- package/dist/chunks/logs-Giq5sUau.mjs.map +1 -0
- package/dist/chunks/migrate-DRoFUg53.mjs +19 -0
- package/dist/chunks/migrate-DRoFUg53.mjs.map +1 -0
- package/dist/chunks/models-Btp6X2YZ.mjs +24 -0
- package/dist/chunks/models-Btp6X2YZ.mjs.map +1 -0
- package/dist/chunks/pause-BASBfKLO.mjs +16 -0
- package/dist/chunks/pause-BASBfKLO.mjs.map +1 -0
- package/dist/chunks/policy-DUIgzF3W.mjs +139 -0
- package/dist/chunks/policy-DUIgzF3W.mjs.map +1 -0
- package/dist/chunks/providers-Dx78imKj.mjs +19 -0
- package/dist/chunks/providers-Dx78imKj.mjs.map +1 -0
- package/dist/chunks/reconcile-BfD785gd.mjs +26 -0
- package/dist/chunks/reconcile-BfD785gd.mjs.map +1 -0
- package/dist/chunks/render-DyZr5Lgi.mjs +53 -0
- package/dist/chunks/render-DyZr5Lgi.mjs.map +1 -0
- package/dist/chunks/resume-GJQRb2Eb.mjs +15 -0
- package/dist/chunks/resume-GJQRb2Eb.mjs.map +1 -0
- package/dist/chunks/retry-CLrUem1H.mjs +15 -0
- package/dist/chunks/retry-CLrUem1H.mjs.map +1 -0
- package/dist/chunks/review-BDdjb6kB.mjs +17 -0
- package/dist/chunks/review-BDdjb6kB.mjs.map +1 -0
- package/dist/chunks/run-CgTWYlcd.mjs +82 -0
- package/dist/chunks/run-CgTWYlcd.mjs.map +1 -0
- package/dist/chunks/run-tool-C3rAv4XF.mjs +26 -0
- package/dist/chunks/run-tool-C3rAv4XF.mjs.map +1 -0
- package/dist/chunks/schema-C9QbUW4H.mjs +578 -0
- package/dist/chunks/schema-C9QbUW4H.mjs.map +1 -0
- package/dist/chunks/send-BdnJ85Nz.mjs +19 -0
- package/dist/chunks/send-BdnJ85Nz.mjs.map +1 -0
- package/dist/chunks/ship-DTuLGoaJ.mjs +19 -0
- package/dist/chunks/ship-DTuLGoaJ.mjs.map +1 -0
- package/dist/chunks/shutdown-BM92Iyv2.mjs +15 -0
- package/dist/chunks/shutdown-BM92Iyv2.mjs.map +1 -0
- package/dist/chunks/skip-DZG5kpcH.mjs +18 -0
- package/dist/chunks/skip-DZG5kpcH.mjs.map +1 -0
- package/dist/chunks/spec-Bq0P3jVh.mjs +20 -0
- package/dist/chunks/spec-Bq0P3jVh.mjs.map +1 -0
- package/dist/chunks/src-CId6kMsY.mjs +1689 -0
- package/dist/chunks/src-CId6kMsY.mjs.map +1 -0
- package/dist/chunks/src-CfAOde7C.mjs +5403 -0
- package/dist/chunks/src-CfAOde7C.mjs.map +1 -0
- package/dist/chunks/src-Cu3TxNob.mjs +783 -0
- package/dist/chunks/src-Cu3TxNob.mjs.map +1 -0
- package/dist/chunks/src-DcU4IXMe.mjs +117 -0
- package/dist/chunks/src-DcU4IXMe.mjs.map +1 -0
- package/dist/chunks/src-JxawBQBn.mjs +446 -0
- package/dist/chunks/src-JxawBQBn.mjs.map +1 -0
- package/dist/chunks/src-s7YRvk0t.mjs +2116 -0
- package/dist/chunks/src-s7YRvk0t.mjs.map +1 -0
- package/dist/chunks/status-SMjiOMYy.mjs +29 -0
- package/dist/chunks/status-SMjiOMYy.mjs.map +1 -0
- package/dist/chunks/tail-BuhT-2PH.mjs +25 -0
- package/dist/chunks/tail-BuhT-2PH.mjs.map +1 -0
- package/dist/chunks/update-BXIppsbi.mjs +16 -0
- package/dist/chunks/update-BXIppsbi.mjs.map +1 -0
- package/dist/cli.mjs +175 -0
- package/dist/cli.mjs.map +1 -0
- package/package.json +17 -32
- package/CHANGELOG.md +0 -1461
- package/bin/cli.js +0 -817
- package/bin/report.js +0 -141
- package/core/adapter/render.js +0 -415
- package/core/agents/implementer.template.md +0 -142
- package/core/agents/profile-reader.md +0 -50
- package/core/agents/release.md +0 -58
- package/core/agents/review.md +0 -179
- package/core/commands/cohorte-align-ds.md +0 -33
- package/core/commands/cohorte-audit.md +0 -58
- package/core/commands/cohorte-brainstorm.md +0 -70
- package/core/commands/cohorte-build.md +0 -219
- package/core/commands/cohorte-doctor.md +0 -194
- package/core/commands/cohorte-fix.md +0 -80
- package/core/commands/cohorte-fleet.md +0 -103
- package/core/commands/cohorte-init-pipeline.md +0 -31
- package/core/commands/cohorte-intake.md +0 -92
- package/core/commands/cohorte-patch.md +0 -118
- package/core/commands/cohorte-refactor.md +0 -59
- package/core/commands/cohorte-retro.md +0 -85
- package/core/commands/cohorte-review.md +0 -270
- package/core/commands/cohorte-ship.md +0 -122
- package/core/commands/cohorte-spec.md +0 -101
- package/core/commands/cohorte-update-pipeline.md +0 -165
- package/core/hooks/gate.py +0 -588
- package/core/runtimes/claude.json +0 -74
- package/core/runtimes/codex.json +0 -85
- package/core/runtimes/cursor.json +0 -76
- package/core/runtimes/gemini.json +0 -76
- package/core/runtimes/opencode.json +0 -73
- package/core/templates/agent-handoff.md +0 -34
- package/core/templates/brainstorm-return.md +0 -36
- package/core/templates/decisions.template.md +0 -42
- package/core/templates/design-brief.md +0 -44
- package/core/templates/patch.template.md +0 -86
- package/core/templates/pr-body.md +0 -25
- package/core/templates/review-feedback.md +0 -38
- package/core/templates/spec.template.md +0 -87
- package/core/templates/steps/init-pipeline/01-detect-stack.md +0 -44
- package/core/templates/steps/init-pipeline/02-interview-gaps.md +0 -55
- package/core/templates/steps/init-pipeline/03-draft-profile.md +0 -10
- package/core/templates/steps/init-pipeline/04-write-render.md +0 -145
- package/core/templates/steps/init-pipeline/05-report.md +0 -12
- package/core/workflows/audit.js +0 -240
- package/core/workflows/loop.js +0 -641
- package/core/workflows/refactor.js +0 -276
- package/core/workflows/review.js +0 -456
- package/install.ps1 +0 -103
- package/install.sh +0 -114
- package/lib/doctor.js +0 -534
- package/lib/runtime.js +0 -139
- package/lib/versions.js +0 -149
- package/lib/yaml.js +0 -129
- package/profile/PIPELINE.template.md +0 -259
- package/profile/SCHEMA.md +0 -735
- package/profile/cohorte.config.template.yaml +0 -43
- package/scripts/assert-gate-hook.mjs +0 -75
- package/scripts/kanban-move.sh +0 -313
- package/scripts/metrics/collect.mjs +0 -531
- package/scripts/metrics/prices.json +0 -42
- package/scripts/new-feature.sh.template +0 -98
- package/scripts/preflight.sh +0 -124
- package/scripts/remove-feature.sh.template +0 -56
- package/scripts/test-adapter.mjs +0 -437
- package/scripts/test-gate.mjs +0 -443
- package/scripts/test-kanban.mjs +0 -304
- package/scripts/test-lib.mjs +0 -356
- package/scripts/test-metrics.mjs +0 -156
- package/scripts/test-workflows.mjs +0 -758
- package/scripts/validate-core.mjs +0 -325
package/bin/report.js
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
// Machine-readable reports for `cohorte specs` and `cohorte doctor`.
|
|
3
|
-
//
|
|
4
|
-
// Both commands read what lib/doctor.js computes (`scanSpecs` and `state`) and
|
|
5
|
-
// render it four ways:
|
|
6
|
-
//
|
|
7
|
-
// default a human table on a terminal
|
|
8
|
-
// --porcelain one record per line, fields separated by U+001F (the ASCII unit
|
|
9
|
-
// separator) — stable, greppable, and immune to a spec title that
|
|
10
|
-
// contains a space, a tab or a pipe
|
|
11
|
-
// --json the native document (the full doctor state, the spec list)
|
|
12
|
-
// --panel the payload shape a Francois extension panel validates against
|
|
13
|
-
// (`{rows:[{key,value,tone}]}` / `{rows:[{id,cells,tone}]}`). This is
|
|
14
|
-
// the ONLY Francois-aware surface in cohorte; everything else here is
|
|
15
|
-
// generic. See github.com/TheBidouilleAgency/francois-plugin-cohorte.
|
|
16
|
-
//
|
|
17
|
-
// Dependency-free, and it never writes anything.
|
|
18
|
-
|
|
19
|
-
const US = String.fromCharCode(0x1f); // ASCII unit separator (U+001F)
|
|
20
|
-
|
|
21
|
-
// A spec's status → a display tone. The tones are the ones a status column can carry
|
|
22
|
-
// anywhere (terminal colour, panel row tone); `busy` marks the two statuses that mean
|
|
23
|
-
// "a command is mid-flight on this spec".
|
|
24
|
-
const STATUS_TONE = {
|
|
25
|
-
draft: 'neutral',
|
|
26
|
-
frozen: 'neutral',
|
|
27
|
-
'in-progress': 'busy',
|
|
28
|
-
'in-review': 'busy',
|
|
29
|
-
shipped: 'ok',
|
|
30
|
-
blocked: 'error',
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
// doctor's four check states → the same tone vocabulary. `skip` is not a failure:
|
|
34
|
-
// a check that does not apply to this project (no design system, no worktrees) reads
|
|
35
|
-
// neutral, never warn.
|
|
36
|
-
const CHECK_TONE = { ok: 'ok', warn: 'warn', bad: 'error', skip: 'neutral' };
|
|
37
|
-
|
|
38
|
-
// Collapse anything that would break the line format or a single-line cell. A spec
|
|
39
|
-
// title is free text written by a human in YAML frontmatter — it can contain a newline
|
|
40
|
-
// continuation, and it must never split one record into two. A stray separator inside a
|
|
41
|
-
// field goes the same way, and so does the padding a doctor `fix` carries for terminal
|
|
42
|
-
// alignment — in a one-line panel cell that padding reads as a hole.
|
|
43
|
-
function flat(value) {
|
|
44
|
-
return String(value == null ? '' : value)
|
|
45
|
-
.replace(new RegExp(`[\\s${US}]+`, 'g'), ' ')
|
|
46
|
-
.trim();
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function specRecords(projectRoot, scanSpecs) {
|
|
50
|
-
return scanSpecs(projectRoot).map((s) => ({
|
|
51
|
-
id: s.id,
|
|
52
|
-
title: s.title || '',
|
|
53
|
-
status: s.status || 'unknown',
|
|
54
|
-
branch: s.branch || '',
|
|
55
|
-
file: s.file,
|
|
56
|
-
tone: STATUS_TONE[s.status] || 'warn', // an unknown status IS the warning
|
|
57
|
-
}));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function specsPorcelain(records) {
|
|
61
|
-
return records
|
|
62
|
-
.map((r) => [r.id, r.title, r.status, r.branch, r.tone].map(flat).join(US))
|
|
63
|
-
.join('\n');
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function specsPanel(records) {
|
|
67
|
-
return JSON.stringify({
|
|
68
|
-
rows: records.map((r) => ({
|
|
69
|
-
id: flat(r.id),
|
|
70
|
-
tone: r.tone,
|
|
71
|
-
cells: { id: flat(r.id), title: flat(r.title), status: flat(r.status), branch: flat(r.branch) },
|
|
72
|
-
})),
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function specsHuman(records) {
|
|
77
|
-
if (!records.length) return 'no specs in ./specs — /cohorte-spec writes the first one';
|
|
78
|
-
const w = (k) => Math.max(...records.map((r) => flat(r[k]).length), k.length);
|
|
79
|
-
const [wi, ws, wb] = [w('id'), w('status'), w('branch')];
|
|
80
|
-
const head = `${'id'.padEnd(wi)} ${'status'.padEnd(ws)} ${'branch'.padEnd(wb)} title`;
|
|
81
|
-
const rows = records.map(
|
|
82
|
-
(r) => `${flat(r.id).padEnd(wi)} ${flat(r.status).padEnd(ws)} ${flat(r.branch).padEnd(wb)} ${flat(r.title)}`,
|
|
83
|
-
);
|
|
84
|
-
return [head, '-'.repeat(head.length), ...rows].join('\n');
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function checkRecords(state) {
|
|
88
|
-
return state.checks.map((c) => ({
|
|
89
|
-
id: c.id,
|
|
90
|
-
label: c.label,
|
|
91
|
-
status: c.status,
|
|
92
|
-
detail: c.detail,
|
|
93
|
-
fix: c.fix || '',
|
|
94
|
-
tone: CHECK_TONE[c.status] || 'neutral',
|
|
95
|
-
}));
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function doctorPorcelain(records) {
|
|
99
|
-
return records
|
|
100
|
-
.map((r) => [r.id, r.label, r.status, r.detail, r.fix].map(flat).join(US))
|
|
101
|
-
.join('\n');
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// key-value, one row per check: the check's label keyed against what it found. The fix
|
|
105
|
-
// rides in the value for a failing check — a panel row has nowhere else to put it, and
|
|
106
|
-
// a health report that says "broken" without saying "run this" is half a report.
|
|
107
|
-
function doctorPanel(records) {
|
|
108
|
-
return JSON.stringify({
|
|
109
|
-
rows: records.map((r) => ({
|
|
110
|
-
key: flat(r.label),
|
|
111
|
-
value: flat(r.fix && r.tone !== 'ok' ? `${r.detail} → ${r.fix}` : r.detail),
|
|
112
|
-
tone: r.tone,
|
|
113
|
-
})),
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function doctorHuman(state, records) {
|
|
118
|
-
const mark = { ok: 'ok ', warn: 'warn', bad: 'BAD ', skip: 'skip' };
|
|
119
|
-
const lines = [`cohorte doctor — ${state.project}`, ''];
|
|
120
|
-
for (const r of records) {
|
|
121
|
-
lines.push(`${mark[r.status] || '? '} ${r.label} — ${flat(r.detail)}`);
|
|
122
|
-
if (r.fix && r.status !== 'ok') lines.push(` fix: ${flat(r.fix)}`);
|
|
123
|
-
}
|
|
124
|
-
const s = state.summary;
|
|
125
|
-
lines.push('', `${s.ok} ok · ${s.warn} warn · ${s.bad} bad · ${s.skip} skipped`);
|
|
126
|
-
return lines.join('\n');
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
module.exports = {
|
|
130
|
-
US,
|
|
131
|
-
STATUS_TONE,
|
|
132
|
-
CHECK_TONE,
|
|
133
|
-
specRecords,
|
|
134
|
-
specsPorcelain,
|
|
135
|
-
specsPanel,
|
|
136
|
-
specsHuman,
|
|
137
|
-
checkRecords,
|
|
138
|
-
doctorPorcelain,
|
|
139
|
-
doctorPanel,
|
|
140
|
-
doctorHuman,
|
|
141
|
-
};
|
package/core/adapter/render.js
DELETED
|
@@ -1,415 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// cohorte — runtime adapter.
|
|
3
|
-
//
|
|
4
|
-
// The core prompts (core/commands/*.md, core/agents/*.md) are the SINGLE source of
|
|
5
|
-
// truth and are written runtime-neutral. This module transpiles them into whatever
|
|
6
|
-
// the target coding agent actually reads:
|
|
7
|
-
//
|
|
8
|
-
// commands agents gate hook
|
|
9
|
-
// claude .claude/commands/<n>.md md, $ARGUMENTS md PreToolUse (deny+ask)
|
|
10
|
-
// codex .agents/skills/<n>/SKILL.md md, no substitution toml PreToolUse (deny only)
|
|
11
|
-
// cursor .cursor/commands/<n>.md md, no substitution md beforeShellExecution
|
|
12
|
-
// gemini .gemini/commands/<n>.toml toml, {{args}} md BeforeTool (deny only)
|
|
13
|
-
// opencode .opencode/commands/<n>.md md, $ARGUMENTS md none — advisory --check
|
|
14
|
-
//
|
|
15
|
-
// Three transforms, in order:
|
|
16
|
-
//
|
|
17
|
-
// 1. capability conditionals — `<!-- cohorte:if hooks -->…<!-- cohorte:else -->…
|
|
18
|
-
// <!-- cohorte:endif -->` keeps exactly one branch, so one file can state both "the gate
|
|
19
|
-
// fires whether or not you cooperate" and "you must call the gate yourself".
|
|
20
|
-
// 2. runtime preamble — one block resolving `<core>`, `<agents>`, the dispatch verb and
|
|
21
|
-
// the gate mechanism, so the prose below never hardcodes a runtime's layout.
|
|
22
|
-
// 3. surface encoding — frontmatter filtered to the keys that runtime understands (an
|
|
23
|
-
// unknown key is prose the model reads as an instruction, so dropping is not cosmetic),
|
|
24
|
-
// arg placeholder swapped, and the whole thing re-emitted as md, toml or a skill dir.
|
|
25
|
-
//
|
|
26
|
-
// Subagents are NOT one of the conditionals: they are a precondition (see assertSupported).
|
|
27
|
-
//
|
|
28
|
-
// Claude output is byte-identical to the pre-adapter core when a file has no conditionals:
|
|
29
|
-
// that is the regression test (scripts/test-adapter.mjs).
|
|
30
|
-
|
|
31
|
-
'use strict';
|
|
32
|
-
|
|
33
|
-
const fs = require('fs');
|
|
34
|
-
const os = require('os');
|
|
35
|
-
const path = require('path');
|
|
36
|
-
|
|
37
|
-
const RUNTIME_DIR = path.join(__dirname, '..', 'runtimes');
|
|
38
|
-
|
|
39
|
-
// --- registry ----------------------------------------------------------------
|
|
40
|
-
|
|
41
|
-
function listRuntimes() {
|
|
42
|
-
return fs.readdirSync(RUNTIME_DIR)
|
|
43
|
-
.filter((f) => f.endsWith('.json'))
|
|
44
|
-
.map((f) => f.slice(0, -5))
|
|
45
|
-
.sort();
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function loadRuntime(id) {
|
|
49
|
-
const file = path.join(RUNTIME_DIR, `${id}.json`);
|
|
50
|
-
if (!fs.existsSync(file)) {
|
|
51
|
-
throw new Error(`unknown runtime "${id}" (known: ${listRuntimes().join(', ')})`);
|
|
52
|
-
}
|
|
53
|
-
const rt = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
54
|
-
assertSupported(rt);
|
|
55
|
-
return rt;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Subagents are a HARD requirement, not a capability to branch on. The pipeline's premise is
|
|
59
|
-
// that each surface is built by someone who can only see the frozen contract; without real
|
|
60
|
-
// subagents the lead does every surface in one context and that isolation is simply gone.
|
|
61
|
-
// A sequential-persona fallback existed briefly and was removed: it asked the lead to simulate
|
|
62
|
-
// the boundary by discipline, which is not the same guarantee, and no supported runtime ever
|
|
63
|
-
// took that branch. Refuse loudly rather than render a pipeline whose central promise is absent.
|
|
64
|
-
function assertSupported(rt) {
|
|
65
|
-
if (rt.capabilities && rt.capabilities.subagents === false) {
|
|
66
|
-
throw new Error(`runtime "${rt.id}" declares no subagents — cohorte requires them `
|
|
67
|
-
+ '(see docs/reference/runtimes.md §Requirements)');
|
|
68
|
-
}
|
|
69
|
-
return rt;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function expandHome(p) {
|
|
73
|
-
if (!p) return p;
|
|
74
|
-
return p.startsWith('~/') ? path.join(os.homedir(), p.slice(2)) : p;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Which runtimes are actually installed on this machine. Detection is by config
|
|
78
|
-
// directory, not by binary on PATH: Cursor ships no CLI, and a runtime the human
|
|
79
|
-
// has configured but not opened this shell for is still a legitimate target.
|
|
80
|
-
function detectRuntimes() {
|
|
81
|
-
return listRuntimes().filter((id) => {
|
|
82
|
-
const rt = loadRuntime(id);
|
|
83
|
-
return (rt.detect || []).some((d) => fs.existsSync(expandHome(d)));
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Absolute destinations for one (runtime, scope) pair. `projectRoot` anchors every
|
|
88
|
-
// relative path; `~`-rooted ones ignore it. A scope entry may pin an individual
|
|
89
|
-
// surface to another scope (Codex reads prompts ONLY from ~/.codex, even when the
|
|
90
|
-
// neutral core is bundled in the repo) via `<key>_scope`.
|
|
91
|
-
function resolvePaths(runtime, scope, projectRoot, { overrides } = {}) {
|
|
92
|
-
const spec = runtime.scopes[scope];
|
|
93
|
-
if (!spec) throw new Error(`runtime ${runtime.id} has no "${scope}" scope`);
|
|
94
|
-
// `overrides` re-roots a declared prefix — the one caller is CLAUDE_CONFIG_DIR, which moves
|
|
95
|
-
// Claude Code's whole `~/.claude` tree elsewhere (a desktop host points it at
|
|
96
|
-
// `~/Library/Application Support/…`). Ignoring it split the install in half: the core was
|
|
97
|
-
// written to `~/.claude` while the hook was registered in the overridden dir, so a scratch
|
|
98
|
-
// install silently wrote into the user's real global core.
|
|
99
|
-
const reroot = (p) => {
|
|
100
|
-
for (const [from, to] of Object.entries(overrides || {})) {
|
|
101
|
-
if (p === from) return to;
|
|
102
|
-
if (p.startsWith(from + '/')) return to + p.slice(from.length);
|
|
103
|
-
}
|
|
104
|
-
return p;
|
|
105
|
-
};
|
|
106
|
-
const abs = (p) => {
|
|
107
|
-
if (!p) return null;
|
|
108
|
-
const e = expandHome(reroot(p));
|
|
109
|
-
return path.isAbsolute(e) ? e : path.join(projectRoot, e);
|
|
110
|
-
};
|
|
111
|
-
const out = { scope, effective: {} };
|
|
112
|
-
for (const key of ['root', 'core', 'commands', 'agents', 'hooks', 'hooks_config',
|
|
113
|
-
'workflows', 'settings']) {
|
|
114
|
-
out[key] = abs(spec[key]);
|
|
115
|
-
out.effective[key] = spec[`${key}_scope`] || scope;
|
|
116
|
-
}
|
|
117
|
-
return out;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// --- 1. capability conditionals ----------------------------------------------
|
|
121
|
-
|
|
122
|
-
const IF = /^\s*<!--\s*cohorte:if\s+([^>]+?)\s*-->\s*$/;
|
|
123
|
-
const ELSE = /^\s*<!--\s*cohorte:else\s*-->\s*$/;
|
|
124
|
-
const ENDIF = /^\s*<!--\s*cohorte:endif\s*-->\s*$/;
|
|
125
|
-
|
|
126
|
-
// A condition is a space-separated OR of terms; a term is `cap`, `!cap`, `runtime:<id>`
|
|
127
|
-
// or `!runtime:<id>`. OR (not AND) because every real use is "this family of runtimes".
|
|
128
|
-
function testCondition(expr, runtime) {
|
|
129
|
-
return expr.split(/\s+/).filter(Boolean).some((term) => {
|
|
130
|
-
const neg = term.startsWith('!');
|
|
131
|
-
const name = neg ? term.slice(1) : term;
|
|
132
|
-
let value;
|
|
133
|
-
if (name.startsWith('runtime:')) value = runtime.id === name.slice(8);
|
|
134
|
-
else if (name in (runtime.capabilities || {})) value = !!runtime.capabilities[name];
|
|
135
|
-
else throw new Error(`unknown cohorte:if term "${name}"`);
|
|
136
|
-
return neg ? !value : value;
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function applyConditionals(text, runtime) {
|
|
141
|
-
const lines = text.split('\n');
|
|
142
|
-
const out = [];
|
|
143
|
-
// Each frame: {keep} — whether the branch currently being read survives. Nesting is
|
|
144
|
-
// supported so a hooks-branch can carry a subagents-branch inside it.
|
|
145
|
-
const stack = [];
|
|
146
|
-
const emitting = () => stack.every((f) => f.keep);
|
|
147
|
-
for (const line of lines) {
|
|
148
|
-
let m;
|
|
149
|
-
if ((m = line.match(IF))) {
|
|
150
|
-
const taken = testCondition(m[1], runtime);
|
|
151
|
-
stack.push({ taken, keep: taken, seenElse: false });
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
if (ELSE.test(line)) {
|
|
155
|
-
const frame = stack[stack.length - 1];
|
|
156
|
-
if (!frame) throw new Error('cohorte:else without a matching cohorte:if');
|
|
157
|
-
if (frame.seenElse) throw new Error('two cohorte:else in one cohorte:if');
|
|
158
|
-
frame.seenElse = true;
|
|
159
|
-
frame.keep = !frame.taken;
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
if (ENDIF.test(line)) {
|
|
163
|
-
if (!stack.pop()) throw new Error('cohorte:endif without a matching cohorte:if');
|
|
164
|
-
continue;
|
|
165
|
-
}
|
|
166
|
-
if (emitting()) out.push(line);
|
|
167
|
-
}
|
|
168
|
-
if (stack.length) throw new Error('unclosed cohorte:if');
|
|
169
|
-
return out.join('\n');
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// --- 2. runtime preamble ------------------------------------------------------
|
|
173
|
-
|
|
174
|
-
// Path as the human/model should see it: `~/…` reads better than an expanded homedir,
|
|
175
|
-
// and a project-relative path must stay relative (the agent's cwd is the repo).
|
|
176
|
-
function displayPath(p, projectRoot) {
|
|
177
|
-
if (!p) return null;
|
|
178
|
-
const home = os.homedir();
|
|
179
|
-
if (p.startsWith(home + path.sep)) return '~/' + path.relative(home, p).split(path.sep).join('/');
|
|
180
|
-
if (p.startsWith(projectRoot + path.sep)) return path.relative(projectRoot, p).split(path.sep).join('/');
|
|
181
|
-
return p;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// The per-project directory holding everything the pipeline GENERATES for this repo
|
|
185
|
-
// (gate-config.json, preflight.ok, pipeline-metrics.jsonl, pipeline.json). Distinct from
|
|
186
|
-
// `<core>`: the core can be global while this is always in the repo, next to the code it
|
|
187
|
-
// describes. `.claude` on a Claude install — unchanged, so existing repos keep working.
|
|
188
|
-
//
|
|
189
|
-
// Deliberately SHARED across non-Claude runtimes (`.cohorte`), while each of their cores is
|
|
190
|
-
// its own subdirectory (`.cohorte/<id>`). The state describes the PROJECT — one gate config,
|
|
191
|
-
// one preflight stamp, one metrics log — and duplicating it per runtime would let a repo
|
|
192
|
-
// driven from two agents disagree with itself about what is gated and what is verified.
|
|
193
|
-
// The core, by contrast, is rendered per runtime and cannot be shared: the same template
|
|
194
|
-
// resolves differently depending on whether that agent has subagents or hooks.
|
|
195
|
-
function stateDir(runtime) {
|
|
196
|
-
return runtime.id === 'claude' ? '.claude' : '.cohorte';
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// The user-level config (kanban boards, shared vault). One file per
|
|
200
|
-
// human, not per project or per runtime; the shipped scripts probe the same two paths.
|
|
201
|
-
function configPath(runtime) {
|
|
202
|
-
return runtime.id === 'claude' ? '~/.claude/cohorte.config.yaml' : '~/.cohorte/cohorte.config.yaml';
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function preamble(runtime, paths, projectRoot, { kind = 'command' } = {}) {
|
|
206
|
-
const caps = runtime.capabilities || {};
|
|
207
|
-
const core = displayPath(paths.core, projectRoot);
|
|
208
|
-
const fixedAgentsDir = paths.agents ? displayPath(paths.agents, projectRoot) : `${core}/agents`;
|
|
209
|
-
const agentsDir = runtime.id === 'codex' ? '.codex/agents' : fixedAgentsDir;
|
|
210
|
-
const L = [];
|
|
211
|
-
L.push(`> **Runtime: ${runtime.label}.** Generated by the cohorte adapter — do not edit this file;`);
|
|
212
|
-
L.push(`> edit \`core/${kind === 'agent' ? 'agents' : 'commands'}/\` in the cohorte source and re-install.`);
|
|
213
|
-
L.push('>');
|
|
214
|
-
L.push(`> - \`<core>\` = \`${core}\` — the pipeline's shared assets (\`pipeline/scripts/\`, \`pipeline/SCHEMA.md\`, \`templates/\`). Every \`<core>/…\` path below resolves there, and nowhere else.`);
|
|
215
|
-
L.push(`> - \`<state>\` = \`${stateDir(runtime)}/\` in **this repo** — what the pipeline generates for this project (\`gate-config.json\`, \`preflight.ok\`, \`pipeline-metrics.jsonl\`, \`pipeline.json\`). Always project-relative, even when \`<core>\` is global.`);
|
|
216
|
-
L.push(`> - \`<memory>\` = \`${runtime.memory}\` — this runtime's project-instructions file at the repo root, loaded into every session here. Where the doctrine says to reference or extend it, that is the file.`);
|
|
217
|
-
L.push(`> - \`<config>\` = \`${configPath(runtime)}\` — your user-level config (kanban boards, shared vault). One per human, never committed.`);
|
|
218
|
-
|
|
219
|
-
L.push(`> - \`<agents>\` = \`${agentsDir}\` — real subagents. Dispatch: ${runtime.agent.dispatch}.`);
|
|
220
|
-
L.push(`> - \`<fixed-agents>\` = \`${fixedAgentsDir}\` — shipped review, release and profile-reader agents. Agent extension: \`${runtime.agent.ext}\`.`);
|
|
221
|
-
if (runtime.id === 'codex') {
|
|
222
|
-
L.push('> - Surface agents are always project-local `.codex/agents/*.toml`, even with a global core. Keep the normal user `CODEX_HOME`; no project launcher or authentication symlink is needed. Never overwrite another project\'s global agents.');
|
|
223
|
-
L.push('> - Agent files use TOML `name`, `description`, `developer_instructions`; optional `model` and `model_reasoning_effort` must be Codex-compatible. Omitted model settings inherit; never write Anthropic aliases. MCP registration lives in `.codex/config.toml`, not a standalone `.mcp.json`.');
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (caps.hooks) {
|
|
227
|
-
const cfg = displayPath(paths.hooks_config, projectRoot);
|
|
228
|
-
const ask = runtime.hook.supports_ask
|
|
229
|
-
? 'It can deny outright or ask you to confirm.'
|
|
230
|
-
: 'This runtime has **no confirmation tier**, so a command that would merely be queried elsewhere is **denied** here — re-run it yourself if you meant it.';
|
|
231
|
-
L.push(`> - **Gate** — \`<core>/hooks/gate.py\` is registered as a blocking \`${runtime.hook.event}\` hook in \`${cfg}\`, reading \`<state>/gate-config.json\`. It fires whether or not you cooperate. ${ask}`);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (!caps.hooks) {
|
|
235
|
-
L.push('> - **Gate** — this runtime has no blocking hook, so the gate is not automatic. Before any command listed in the profile\'s `gate` block, and before every phase dispatch, run `<core>/hooks/gate.py --check <command>` yourself and obey its verdict (`deny` ⇒ stop, `ask` ⇒ get the human\'s explicit go-ahead). Skipping this is the one deviation that silently removes a safety property.');
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (!caps.workflows) {
|
|
239
|
-
L.push('> - **Workflows** — unavailable on this runtime. The conversational path below is the only path; ignore any mention of a workflow variant.');
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (!runtime.command.args) {
|
|
243
|
-
L.push(`> - \`$ARGUMENTS\` — this runtime does not substitute placeholders. It means ${runtime.command.args_note || 'the text typed after the command name'}; expand it yourself everywhere it appears below.`);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return L.join('\n') + '\n';
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Apply to commands AND the instruction templates they consume. Filenames here refer to
|
|
250
|
-
// rendered agents, not to the Markdown source templates shipped inside the core.
|
|
251
|
-
function adaptInstructions(text, runtime) {
|
|
252
|
-
const resolved = applyConditionals(text, runtime);
|
|
253
|
-
if (runtime.id !== 'codex') return resolved;
|
|
254
|
-
return resolved.replace(/(<(?:agents|fixed-agents)>\/[^\s`]+)\.md\b/g, '$1.toml');
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// --- 3. surface encoding ------------------------------------------------------
|
|
258
|
-
|
|
259
|
-
function parseFrontmatter(text) {
|
|
260
|
-
if (!text.startsWith('---\n')) return { keys: [], body: text };
|
|
261
|
-
const end = text.indexOf('\n---\n', 3);
|
|
262
|
-
if (end === -1) return { keys: [], body: text };
|
|
263
|
-
const block = text.slice(4, end + 1);
|
|
264
|
-
const body = text.slice(end + 5);
|
|
265
|
-
const keys = [];
|
|
266
|
-
for (const line of block.split('\n')) {
|
|
267
|
-
const m = line.match(/^([A-Za-z][\w-]*):\s?(.*)$/);
|
|
268
|
-
// Continuation lines (a wrapped value) belong to the previous key.
|
|
269
|
-
if (!m) {
|
|
270
|
-
if (line.trim() && keys.length) keys[keys.length - 1][1] += '\n' + line;
|
|
271
|
-
continue;
|
|
272
|
-
}
|
|
273
|
-
keys.push([m[1], m[2]]);
|
|
274
|
-
}
|
|
275
|
-
return { keys, body };
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
function emitFrontmatter(keys) {
|
|
279
|
-
if (!keys.length) return '';
|
|
280
|
-
return '---\n' + keys.map(([k, v]) => `${k}: ${v}`).join('\n') + '\n---\n\n';
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function substituteArgs(text, runtime) {
|
|
284
|
-
const token = runtime.command.args;
|
|
285
|
-
if (!token || token === '$ARGUMENTS') return text;
|
|
286
|
-
return text.split('$ARGUMENTS').join(token);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Prefer readable multi-line literal TOML; fall back to basic strings for delimiter collisions.
|
|
290
|
-
function tomlMultiline(s) {
|
|
291
|
-
// Literal TOML strings cannot escape their delimiter. Use a basic string when the
|
|
292
|
-
// prompt itself contains triple apostrophes, preserving the content exactly.
|
|
293
|
-
return s.includes("'''") ? JSON.stringify(s) : "'''\n" + s + "\n'''";
|
|
294
|
-
}
|
|
295
|
-
function tomlBasic(s) {
|
|
296
|
-
return '"' + s.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, ' ') + '"';
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Render one core command for one runtime.
|
|
301
|
-
* @returns {{filename: string, content: string, dir: 'commands'}}
|
|
302
|
-
*/
|
|
303
|
-
function renderCommand({ source, name, runtime, paths, projectRoot }) {
|
|
304
|
-
const { keys, body } = parseFrontmatter(source);
|
|
305
|
-
let out = adaptInstructions(body, runtime);
|
|
306
|
-
out = preamble(runtime, paths, projectRoot) + '\n' + out.replace(/^\n+/, '');
|
|
307
|
-
out = substituteArgs(out, runtime);
|
|
308
|
-
|
|
309
|
-
const kept = keys.filter(([k]) => runtime.command.frontmatter.includes(k));
|
|
310
|
-
const dropped = keys.filter(([k]) => !runtime.command.frontmatter.includes(k)).map(([k]) => k);
|
|
311
|
-
|
|
312
|
-
// A skill is a DIRECTORY — `<name>/SKILL.md` — whose frontmatter must carry `name`, since
|
|
313
|
-
// that is what the runtime matches on (both for `$<name>` and for implicit selection from
|
|
314
|
-
// `description`). Command sources have no `name` key, so synthesise it from the filename.
|
|
315
|
-
if (runtime.command.format === 'skill') {
|
|
316
|
-
if (!kept.some(([k]) => k === 'name')) kept.unshift(['name', name]);
|
|
317
|
-
return { filename: `${name}${runtime.command.ext}`, content: emitFrontmatter(kept) + out, dropped };
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
if (runtime.command.format === 'toml') {
|
|
321
|
-
const desc = (keys.find(([k]) => k === 'description') || [null, ''])[1];
|
|
322
|
-
const lines = [`# cohorte — generated for ${runtime.label}. Do not edit; edit core/commands/${name}.md.`];
|
|
323
|
-
if (dropped.length) lines.push(`# frontmatter not supported here, dropped: ${dropped.join(', ')}`);
|
|
324
|
-
if (desc) lines.push(`description = ${tomlBasic(desc)}`);
|
|
325
|
-
lines.push(`prompt = ${tomlMultiline(substituteArgs(out, runtime))}`);
|
|
326
|
-
return { filename: `${name}${runtime.command.ext}`, content: lines.join('\n') + '\n', dropped };
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
const header = emitFrontmatter(kept);
|
|
330
|
-
return { filename: `${name}${runtime.command.ext}`, content: header + out, dropped };
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// Is this agent read-only? Derived from the source's Claude-style `tools:` list rather than
|
|
334
|
-
// declared twice: an agent that was never given Write, Edit or Bash is read-only by intent,
|
|
335
|
-
// and each runtime spells that its own way (`readonly: true`, `sandbox_mode = "read-only"`).
|
|
336
|
-
// Losing it silently would hand the reviewer the ability to fix what it is meant to report.
|
|
337
|
-
function isReadOnly(keys) {
|
|
338
|
-
const tools = (keys.find(([k]) => k === 'tools') || [])[1];
|
|
339
|
-
if (!tools) return false;
|
|
340
|
-
// The implementer template is rendered before its surface tool list is known.
|
|
341
|
-
// A placeholder is not evidence that the eventual worker must be read-only.
|
|
342
|
-
if (tools.includes('<SURFACE_TOOLS>')) return false;
|
|
343
|
-
return !/\b(Write|Edit|MultiEdit|Bash|NotebookEdit)\b/.test(tools);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* Render one core agent for one runtime.
|
|
348
|
-
*
|
|
349
|
-
* Every runtime cohorte targets has real subagents, but they disagree on the file: markdown +
|
|
350
|
-
* frontmatter for Claude Code, Cursor, Gemini CLI and OpenCode; TOML with the body under
|
|
351
|
-
* `developer_instructions` for Codex. `format: "persona"` remains for a runtime with no
|
|
352
|
-
* subagents at all — the file is then read and adopted by the lead in sequence.
|
|
353
|
-
*/
|
|
354
|
-
function renderAgent({ source, name, runtime, paths, projectRoot }) {
|
|
355
|
-
const { keys, body } = parseFrontmatter(source);
|
|
356
|
-
const out = preamble(runtime, paths, projectRoot, { kind: 'agent' }) + '\n'
|
|
357
|
-
+ adaptInstructions(body, runtime).replace(/^\n+/, '');
|
|
358
|
-
const spec = runtime.agent;
|
|
359
|
-
const readonly = isReadOnly(keys);
|
|
360
|
-
|
|
361
|
-
if (spec.format === 'toml') {
|
|
362
|
-
const get = (k) => (keys.find(([kk]) => kk === k) || [null, ''])[1];
|
|
363
|
-
const lines = [`# cohorte — generated for ${runtime.label}. Do not edit; edit core/agents/${name}.md.`];
|
|
364
|
-
lines.push(`name = ${tomlBasic(get('name') || name)}`);
|
|
365
|
-
if (get('description')) lines.push(`description = ${tomlBasic(get('description'))}`);
|
|
366
|
-
const model = get('model');
|
|
367
|
-
if (spec.frontmatter.includes('model') && model
|
|
368
|
-
&& !['inherit', 'sonnet', 'haiku', 'opus'].includes(model) && !model.startsWith('<')) {
|
|
369
|
-
lines.push(`model = ${tomlBasic(model)}`);
|
|
370
|
-
}
|
|
371
|
-
if (spec.frontmatter.includes('model_reasoning_effort') && get('model_reasoning_effort')) {
|
|
372
|
-
lines.push(`model_reasoning_effort = ${tomlBasic(get('model_reasoning_effort'))}`);
|
|
373
|
-
}
|
|
374
|
-
if (readonly && spec.readonly_key) {
|
|
375
|
-
lines.push(`${spec.readonly_key} = ${tomlBasic(spec.readonly_value)}`);
|
|
376
|
-
}
|
|
377
|
-
lines.push(`${spec.body_key} = ${tomlMultiline(out)}`);
|
|
378
|
-
return { filename: `${name}${spec.ext}`, content: lines.join('\n') + '\n', native: true, readonly };
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
if (spec.format !== 'md') {
|
|
382
|
-
return { filename: `${name}.md`, content: out, native: false, dropped: keys.map(([k]) => k) };
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
const allowed = spec.frontmatter;
|
|
386
|
-
const kept = keys.filter(([k]) => allowed.includes(k));
|
|
387
|
-
for (const [k, v] of Object.entries(spec.defaults || {})) {
|
|
388
|
-
if (!kept.some(([kk]) => kk === k)) kept.push([k, v]);
|
|
389
|
-
}
|
|
390
|
-
if (readonly && spec.readonly_key && !kept.some(([k]) => k === spec.readonly_key)) {
|
|
391
|
-
kept.push([spec.readonly_key, spec.readonly_value]);
|
|
392
|
-
}
|
|
393
|
-
const dropped = keys.filter(([k]) => !allowed.includes(k)).map(([k]) => k);
|
|
394
|
-
return { filename: `${name}${spec.ext || '.md'}`, content: emitFrontmatter(kept) + out, native: true, dropped, readonly };
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
module.exports = {
|
|
398
|
-
assertSupported,
|
|
399
|
-
stateDir,
|
|
400
|
-
configPath,
|
|
401
|
-
listRuntimes,
|
|
402
|
-
loadRuntime,
|
|
403
|
-
detectRuntimes,
|
|
404
|
-
resolvePaths,
|
|
405
|
-
expandHome,
|
|
406
|
-
displayPath,
|
|
407
|
-
applyConditionals,
|
|
408
|
-
adaptInstructions,
|
|
409
|
-
testCondition,
|
|
410
|
-
parseFrontmatter,
|
|
411
|
-
emitFrontmatter,
|
|
412
|
-
preamble,
|
|
413
|
-
renderCommand,
|
|
414
|
-
renderAgent,
|
|
415
|
-
};
|