orchestrator-workflow 0.40.0 → 0.41.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/CHANGELOG.md +103 -16
- package/README.md +31 -5
- package/assets/agents/advisor.md +5 -0
- package/assets/agents/explorer.md +9 -3
- package/assets/agents/implementer.md +35 -7
- package/assets/agents/reviewer.md +36 -5
- package/assets/agents/task-slicer.md +23 -2
- package/assets/agents-md-section.md +55 -0
- package/assets/skill/SKILL.md +54 -6
- package/assets/skill/references/bundle-gate-in-ci.md +193 -0
- package/assets/skill/references/contracts.md +43 -8
- package/assets/skill/references/evidence-and-probes.md +76 -8
- package/assets/skill/references/review-and-recovery.md +9 -1
- package/assets/skill/references/run-state-and-harness.md +31 -0
- package/assets/templates/00-goal.md +8 -0
- package/assets/templates/02-tasks.md +6 -2
- package/assets/templates/06-handoff.md +22 -2
- package/dist/cli.js +6 -0
- package/dist/doctor.d.ts +13 -0
- package/dist/doctor.js +61 -1
- package/dist/init.d.ts +69 -0
- package/dist/init.js +133 -1
- package/package.json +1 -1
package/dist/init.d.ts
CHANGED
|
@@ -70,6 +70,32 @@ export interface InitOptions {
|
|
|
70
70
|
* too, the same as `null`.
|
|
71
71
|
*/
|
|
72
72
|
pin?: string | null;
|
|
73
|
+
/**
|
|
74
|
+
* Configured knowledge-bundle locations, or `undefined` to carry the
|
|
75
|
+
* previous manifest's `knowledge` forward unchanged (the same
|
|
76
|
+
* omitted-means-sticky convention `routing`/`pin` already use). A present
|
|
77
|
+
* array (including `[]`) replaces the previous value outright and is
|
|
78
|
+
* validated with {@link validateKnowledgeBundles}: an invalid entry throws
|
|
79
|
+
* rather than writing a malformed record. Absent (never set on any
|
|
80
|
+
* install) or `[]` means today's behaviour: `docs/okf/` is the sole,
|
|
81
|
+
* implicit bundle location every kit site assumes. The CLI has no flag for
|
|
82
|
+
* this; operators hand-edit the manifest field instead.
|
|
83
|
+
*/
|
|
84
|
+
knowledge?: KnowledgeBundle[];
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* One configured knowledge-bundle location. `path` is the bundle directory
|
|
88
|
+
* (for example `docs/okf`) and `repoRoot` the root of the repository the
|
|
89
|
+
* bundle's sources live in (`"."` when that is the worktree itself, a
|
|
90
|
+
* sub-repo's path for a workspace-level bundle whose sources live
|
|
91
|
+
* elsewhere). Each is resolved against the worktree top level on its own:
|
|
92
|
+
* `path` is not nested under `repoRoot`. Both are stored normalised
|
|
93
|
+
* (see {@link checkKnowledgeEntry}) and must not escape the worktree top
|
|
94
|
+
* level; no check argv lives here (see verification-set docs).
|
|
95
|
+
*/
|
|
96
|
+
export interface KnowledgeBundle {
|
|
97
|
+
path: string;
|
|
98
|
+
repoRoot: string;
|
|
73
99
|
}
|
|
74
100
|
export declare const MANIFEST_PATH: string;
|
|
75
101
|
export interface Manifest extends OpencodeModelMaps {
|
|
@@ -114,6 +140,17 @@ export interface Manifest extends OpencodeModelMaps {
|
|
|
114
140
|
* recorded" and therefore never sticky.
|
|
115
141
|
*/
|
|
116
142
|
harnessesRecordedEmpty?: boolean;
|
|
143
|
+
/**
|
|
144
|
+
* Configured knowledge-bundle locations. Absent when never configured on
|
|
145
|
+
* any install of this repo: every kit site that reads this field then
|
|
146
|
+
* falls back to the implicit `docs/okf/` default, today's behaviour; an
|
|
147
|
+
* empty list means the same. When present, an entry that fails
|
|
148
|
+
* validation (a non-relative or top-level-escaping `path`/`repoRoot`) is
|
|
149
|
+
* dropped rather than carried forward, the same per-entry degradation
|
|
150
|
+
* style `files`/`models` above already use for a hand-written or damaged
|
|
151
|
+
* manifest; `doctor` reports each dropped entry.
|
|
152
|
+
*/
|
|
153
|
+
knowledge?: KnowledgeBundle[];
|
|
117
154
|
}
|
|
118
155
|
/**
|
|
119
156
|
* A manifest can be hand-written or tampered with, and uninstall deletes by
|
|
@@ -122,6 +159,38 @@ export interface Manifest extends OpencodeModelMaps {
|
|
|
122
159
|
* can never reach an unlink.
|
|
123
160
|
*/
|
|
124
161
|
export declare function isContainedRelativePath(relativePath: string): boolean;
|
|
162
|
+
/**
|
|
163
|
+
* Checks one raw `knowledge` entry. `path` (the bundle directory) and
|
|
164
|
+
* `repoRoot` (the root of the repository its sources live in) are each
|
|
165
|
+
* resolved against the worktree top level, independently of one another, so
|
|
166
|
+
* a workspace-level bundle whose sources live in a sub-repo is
|
|
167
|
+
* `{ path: "docs/okf", repoRoot: "sub" }`. `repoRoot` defaults to `"."`
|
|
168
|
+
* when omitted. Returns the normalised entry, or the reason it is invalid
|
|
169
|
+
* (naming the offending field) for the caller to throw or report.
|
|
170
|
+
*/
|
|
171
|
+
export declare function checkKnowledgeEntry(entry: unknown): {
|
|
172
|
+
bundle: KnowledgeBundle;
|
|
173
|
+
} | {
|
|
174
|
+
reason: string;
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Lists every problem in a raw on-disk `knowledge` value: `[]` when the
|
|
178
|
+
* field is absent or every entry is valid, one item for a non-array value,
|
|
179
|
+
* otherwise one item per invalid entry with its index and reason. `doctor`
|
|
180
|
+
* reports these, since {@link parseKnowledgeBundles} drops such entries on
|
|
181
|
+
* read and a re-install that rewrites the manifest would remove them from
|
|
182
|
+
* disk without notice.
|
|
183
|
+
*/
|
|
184
|
+
export declare function knowledgeEntryProblems(raw: unknown): string[];
|
|
185
|
+
/**
|
|
186
|
+
* Validates an explicitly-supplied `options.knowledge` array for `runInit`
|
|
187
|
+
* with {@link checkKnowledgeEntry} and returns the normalised entries.
|
|
188
|
+
* Throws on the first invalid entry -- explicit input refuses to write a
|
|
189
|
+
* malformed record rather than dropping it (contrast
|
|
190
|
+
* {@link parseKnowledgeBundles}, which degrades a hand-edited/damaged
|
|
191
|
+
* on-disk manifest by dropping bad entries instead of throwing).
|
|
192
|
+
*/
|
|
193
|
+
export declare function validateKnowledgeBundles(knowledge: unknown): KnowledgeBundle[];
|
|
125
194
|
/**
|
|
126
195
|
* Reads the manifest of a previous install, if any. Manifests can be written
|
|
127
196
|
* by hand (manual agent installs) or damaged, so every field is sanitized;
|
package/dist/init.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3
|
-
import { isAbsolute, join, normalize, sep } from "node:path";
|
|
3
|
+
import { isAbsolute, join, normalize, posix, sep } from "node:path";
|
|
4
4
|
import { PACKAGE_VERSION, listSkillReferenceNames, listTemplateNames, readAgentAsset, readAsset, } from "./assets.js";
|
|
5
5
|
import { HARNESSES } from "./detect.js";
|
|
6
6
|
import { CLASS_MODELS, DEFAULT_PROFILE, DEFAULT_TIER, READ_ONLY_ROLES, ROLES, ROLE_TIERS, TIER_DEFS, assertValidModelId, claudeModelValue, isProfile, opencodeModelValue, rolesForProfile, } from "./models.js";
|
|
@@ -25,6 +25,124 @@ export function isContainedRelativePath(relativePath) {
|
|
|
25
25
|
const normalized = normalize(relativePath);
|
|
26
26
|
return normalized !== ".." && !normalized.startsWith(`..${sep}`);
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Normalises one knowledge-bundle path field (`path` or `repoRoot`) and
|
|
30
|
+
* returns it, or a reason string when it is not acceptable. Both fields are
|
|
31
|
+
* worktree-relative, so spellings of the same location compare equal after
|
|
32
|
+
* this step: POSIX `normalize`, then any trailing `/` stripped (`docs/okf/`,
|
|
33
|
+
* `./docs/okf` and `docs/okf` all become `docs/okf`). An empty string, an
|
|
34
|
+
* absolute path, and a path that normalises to a `..` escape are rejected;
|
|
35
|
+
* `.` (the worktree top level itself) is accepted only when `allowTop` is
|
|
36
|
+
* set, which is the case for `repoRoot` and not for `path`.
|
|
37
|
+
*/
|
|
38
|
+
function normalizeKnowledgePathField(value, allowTop) {
|
|
39
|
+
if (typeof value !== "string")
|
|
40
|
+
return { reason: "is not a string" };
|
|
41
|
+
if (value === "")
|
|
42
|
+
return { reason: "is empty" };
|
|
43
|
+
if (isAbsolute(value) || posix.isAbsolute(value)) {
|
|
44
|
+
return { reason: "is an absolute path" };
|
|
45
|
+
}
|
|
46
|
+
let normalized = posix.normalize(value);
|
|
47
|
+
while (normalized.length > 1 && normalized.endsWith("/")) {
|
|
48
|
+
normalized = normalized.slice(0, -1);
|
|
49
|
+
}
|
|
50
|
+
if (normalized === ".." || normalized.startsWith("../")) {
|
|
51
|
+
return { reason: "escapes the worktree top level" };
|
|
52
|
+
}
|
|
53
|
+
if (normalized === "." && !allowTop) {
|
|
54
|
+
return { reason: "names the worktree top level itself" };
|
|
55
|
+
}
|
|
56
|
+
return { value: normalized };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Checks one raw `knowledge` entry. `path` (the bundle directory) and
|
|
60
|
+
* `repoRoot` (the root of the repository its sources live in) are each
|
|
61
|
+
* resolved against the worktree top level, independently of one another, so
|
|
62
|
+
* a workspace-level bundle whose sources live in a sub-repo is
|
|
63
|
+
* `{ path: "docs/okf", repoRoot: "sub" }`. `repoRoot` defaults to `"."`
|
|
64
|
+
* when omitted. Returns the normalised entry, or the reason it is invalid
|
|
65
|
+
* (naming the offending field) for the caller to throw or report.
|
|
66
|
+
*/
|
|
67
|
+
export function checkKnowledgeEntry(entry) {
|
|
68
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
69
|
+
return { reason: "is not an object" };
|
|
70
|
+
}
|
|
71
|
+
const candidate = entry;
|
|
72
|
+
const path = normalizeKnowledgePathField(candidate.path, false);
|
|
73
|
+
if ("reason" in path)
|
|
74
|
+
return { reason: `path ${path.reason}` };
|
|
75
|
+
const repoRoot = normalizeKnowledgePathField(candidate.repoRoot === undefined ? "." : candidate.repoRoot, true);
|
|
76
|
+
if ("reason" in repoRoot)
|
|
77
|
+
return { reason: `repoRoot ${repoRoot.reason}` };
|
|
78
|
+
return { bundle: { path: path.value, repoRoot: repoRoot.value } };
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Lists every problem in a raw on-disk `knowledge` value: `[]` when the
|
|
82
|
+
* field is absent or every entry is valid, one item for a non-array value,
|
|
83
|
+
* otherwise one item per invalid entry with its index and reason. `doctor`
|
|
84
|
+
* reports these, since {@link parseKnowledgeBundles} drops such entries on
|
|
85
|
+
* read and a re-install that rewrites the manifest would remove them from
|
|
86
|
+
* disk without notice.
|
|
87
|
+
*/
|
|
88
|
+
export function knowledgeEntryProblems(raw) {
|
|
89
|
+
if (raw === undefined)
|
|
90
|
+
return [];
|
|
91
|
+
if (!Array.isArray(raw)) {
|
|
92
|
+
return ["knowledge is not an array and is ignored"];
|
|
93
|
+
}
|
|
94
|
+
const problems = [];
|
|
95
|
+
raw.forEach((entry, index) => {
|
|
96
|
+
const checked = checkKnowledgeEntry(entry);
|
|
97
|
+
if ("reason" in checked) {
|
|
98
|
+
problems.push(`knowledge[${index}] ${checked.reason} and is ignored`);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
return problems;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Validates an explicitly-supplied `options.knowledge` array for `runInit`
|
|
105
|
+
* with {@link checkKnowledgeEntry} and returns the normalised entries.
|
|
106
|
+
* Throws on the first invalid entry -- explicit input refuses to write a
|
|
107
|
+
* malformed record rather than dropping it (contrast
|
|
108
|
+
* {@link parseKnowledgeBundles}, which degrades a hand-edited/damaged
|
|
109
|
+
* on-disk manifest by dropping bad entries instead of throwing).
|
|
110
|
+
*/
|
|
111
|
+
export function validateKnowledgeBundles(knowledge) {
|
|
112
|
+
if (!Array.isArray(knowledge)) {
|
|
113
|
+
throw new Error("options.knowledge must be an array of { path, repoRoot } entries");
|
|
114
|
+
}
|
|
115
|
+
return knowledge.map((entry, index) => {
|
|
116
|
+
const checked = checkKnowledgeEntry(entry);
|
|
117
|
+
if ("reason" in checked) {
|
|
118
|
+
throw new Error(`options.knowledge[${index}] ${checked.reason}; path and repoRoot must be relative paths inside the worktree top level`);
|
|
119
|
+
}
|
|
120
|
+
return checked.bundle;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Sanitizes a possibly hand-edited or damaged on-disk manifest's raw
|
|
125
|
+
* `knowledge` field for {@link readInstalledManifest}: an entry that fails
|
|
126
|
+
* {@link checkKnowledgeEntry} is dropped rather than throwing (the read path
|
|
127
|
+
* can never crash a re-install on tampered input, the same reasoning
|
|
128
|
+
* `files`/`models` already document above), and a valid entry is kept in
|
|
129
|
+
* its normalised spelling. Returns `undefined` when the raw field is absent
|
|
130
|
+
* or not an array, so the "never configured" and "configured but empty"
|
|
131
|
+
* states stay distinct the same way `routing`'s own `"routing" in
|
|
132
|
+
* candidate` check does.
|
|
133
|
+
*/
|
|
134
|
+
function parseKnowledgeBundles(candidate) {
|
|
135
|
+
if (!("knowledge" in candidate) || !Array.isArray(candidate.knowledge)) {
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
const result = [];
|
|
139
|
+
for (const entry of candidate.knowledge) {
|
|
140
|
+
const checked = checkKnowledgeEntry(entry);
|
|
141
|
+
if ("bundle" in checked)
|
|
142
|
+
result.push(checked.bundle);
|
|
143
|
+
}
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
28
146
|
/**
|
|
29
147
|
* Reads the manifest of a previous install, if any. Manifests can be written
|
|
30
148
|
* by hand (manual agent installs) or damaged, so every field is sanitized;
|
|
@@ -102,6 +220,7 @@ export function readInstalledManifest(targetDir) {
|
|
|
102
220
|
if ("routing" in candidate) {
|
|
103
221
|
routing = parseRouting(candidate.routing);
|
|
104
222
|
}
|
|
223
|
+
const knowledge = parseKnowledgeBundles(candidate);
|
|
105
224
|
// A hand-written or damaged manifest may carry a non-string `pin`; that
|
|
106
225
|
// degrades to "no recorded pin" here (the same per-field-degradation
|
|
107
226
|
// style as `profile`/`tiers` above) rather than throwing. An empty or
|
|
@@ -117,6 +236,7 @@ export function readInstalledManifest(targetDir) {
|
|
|
117
236
|
profile,
|
|
118
237
|
tiers,
|
|
119
238
|
...(routing !== undefined ? { routing } : {}),
|
|
239
|
+
...(knowledge !== undefined ? { knowledge } : {}),
|
|
120
240
|
...opencodeMaps,
|
|
121
241
|
files,
|
|
122
242
|
installedAt: typeof candidate.installedAt === "string" ? candidate.installedAt : "",
|
|
@@ -348,6 +468,14 @@ export function runInit(options) {
|
|
|
348
468
|
updateOpencodeModels: options.opencodeModels !== undefined,
|
|
349
469
|
updateOpencodeClassModels: options.opencodeClassModels !== undefined,
|
|
350
470
|
});
|
|
471
|
+
// `undefined` carries the previous manifest's `knowledge` forward
|
|
472
|
+
// unchanged, the same omitted-means-sticky convention `routing`/`pin` use
|
|
473
|
+
// above; a present array (including `[]`) is validated and replaces the
|
|
474
|
+
// previous value outright, throwing before any file is written on an
|
|
475
|
+
// invalid entry rather than persisting a malformed record.
|
|
476
|
+
const knowledge = options.knowledge !== undefined
|
|
477
|
+
? validateKnowledgeBundles(options.knowledge)
|
|
478
|
+
: previous?.knowledge;
|
|
351
479
|
// Check only rendered selections before the first mutation.
|
|
352
480
|
for (const warning of codexCatalogWarnings(routing, { harnesses: options.harnesses, profile, tiers }, options.codexCatalog)) {
|
|
353
481
|
report.notes.push(`Codex catalog: ${warning}`);
|
|
@@ -677,6 +805,7 @@ export function runInit(options) {
|
|
|
677
805
|
profile,
|
|
678
806
|
tiers,
|
|
679
807
|
routing,
|
|
808
|
+
...(knowledge !== undefined ? { knowledge } : {}),
|
|
680
809
|
...compatibility,
|
|
681
810
|
files: installedFiles,
|
|
682
811
|
...(pin !== undefined ? { pin } : {}),
|
|
@@ -691,6 +820,9 @@ export function runInit(options) {
|
|
|
691
820
|
profile: previous.profile,
|
|
692
821
|
tiers: previous.tiers,
|
|
693
822
|
...(previous.routing !== undefined ? { routing: previous.routing } : {}),
|
|
823
|
+
...(previous.knowledge !== undefined
|
|
824
|
+
? { knowledge: previous.knowledge }
|
|
825
|
+
: {}),
|
|
694
826
|
...legacyOpencodeFallbacks(previous),
|
|
695
827
|
files: previous.files,
|
|
696
828
|
...(previous.pin !== undefined ? { pin: previous.pin } : {}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orchestrator-workflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.41.0",
|
|
4
4
|
"description": "Installer for an orchestrator-led agent workflow: .ai/ run state, an AGENTS.md policy section, and per-harness subagent definitions for Claude Code, OpenAI Codex, and opencode",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|