orchestrator-workflow 0.40.1 → 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 +71 -0
- package/README.md +31 -5
- package/assets/agents/advisor.md +5 -0
- package/assets/agents/explorer.md +9 -3
- package/assets/agents/implementer.md +30 -2
- package/assets/agents/reviewer.md +32 -1
- 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 +38 -3
- 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
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
## Bundle gate in CI
|
|
2
|
+
|
|
3
|
+
A knowledge bundle doc rots silently when someone changes one of its
|
|
4
|
+
documented sources outside a run: no hand-off step fires, and the doc keeps
|
|
5
|
+
describing the old code. Running the bundle check in CI on every change closes
|
|
6
|
+
that gap. This reference describes one way to wire it up. GitHub Actions is the
|
|
7
|
+
example host; the same steps work on any CI that can check out full history and
|
|
8
|
+
run a shell step. The kit ships no CI files and generates none; copy and adapt
|
|
9
|
+
the example.
|
|
10
|
+
|
|
11
|
+
The examples use `okf-kit` as the bundle checker, the validator the hand-off
|
|
12
|
+
step names. A different checker works the same way as long as it can report
|
|
13
|
+
findings per rule in a machine-readable form.
|
|
14
|
+
|
|
15
|
+
### Which bundles
|
|
16
|
+
|
|
17
|
+
Gate every configured bundle, not only the default one. Each entry of
|
|
18
|
+
`knowledge` in `.ai/workflow/manifest.json` names a bundle `path` and the
|
|
19
|
+
`repoRoot` its sources live in (default `.`); with no `knowledge` list the one
|
|
20
|
+
bundle is `docs/okf/` with `repoRoot` `.`. Run one check per bundle and pass
|
|
21
|
+
`--repo-root <repoRoot>` explicitly: without it the checker detects the
|
|
22
|
+
repository top level from the bundle directory, which is the wrong root for a
|
|
23
|
+
workspace bundle whose sources live in a sub-repo.
|
|
24
|
+
|
|
25
|
+
### Pin the checker
|
|
26
|
+
|
|
27
|
+
Install the checker at a pinned version, written here as the placeholder
|
|
28
|
+
`okf-kit@<pinned-version>`. An unpinned install picks up new rules on their
|
|
29
|
+
release day and turns an unrelated change red. Bump the pin deliberately, in a
|
|
30
|
+
change of its own, after the new version runs clean against the bundles. The
|
|
31
|
+
example carries the pin as an environment value (`OKF_KIT_VERSION`), so a copy
|
|
32
|
+
that still holds the placeholder fails the job instead of running something
|
|
33
|
+
else.
|
|
34
|
+
|
|
35
|
+
### Check out the full history
|
|
36
|
+
|
|
37
|
+
Check out the full history (`fetch-depth: 0` with `actions/checkout`): the
|
|
38
|
+
`sources-fresh` rule dates each source by its last commit and asks whether the
|
|
39
|
+
doc's own last commit re-stamped the doc, and a shallow clone cannot answer
|
|
40
|
+
that, so it reports `staleness not assessable` notices instead of a STALE
|
|
41
|
+
verdict. A shallow checkout therefore looks clean while it assessed nothing.
|
|
42
|
+
|
|
43
|
+
### Runner as an input
|
|
44
|
+
|
|
45
|
+
Take the runner as an input of the job (for example a reusable workflow input
|
|
46
|
+
`runner`) rather than hard-coding a runner label in the example. The label is a
|
|
47
|
+
property of the consuming organization, not of the gate.
|
|
48
|
+
|
|
49
|
+
### Staged rollout
|
|
50
|
+
|
|
51
|
+
Roll the gate out in three stages and move to the next one only once the
|
|
52
|
+
bundles run clean under the current stage:
|
|
53
|
+
|
|
54
|
+
1. Stage 1, warn-only: run the check on every change, publish every finding as
|
|
55
|
+
an annotation and in the job summary, and never fail the job on a finding.
|
|
56
|
+
This surfaces existing drift without blocking anyone.
|
|
57
|
+
2. Stage 2, block on structure and staleness: fail the job on any
|
|
58
|
+
error-severity finding (structure: frontmatter, links, sources shape) and on
|
|
59
|
+
any `sources-fresh` or `sources-fresh-future` warning, selected from the
|
|
60
|
+
`--json` report with a filter; other warnings stay advisory.
|
|
61
|
+
3. Stage 3, strict: run the check with `--strict`, which fails on every
|
|
62
|
+
warning from every rule.
|
|
63
|
+
|
|
64
|
+
The exit code alone is not the signal for stage 1 or stage 2: `okf-kit check`
|
|
65
|
+
exits 0 when it finds only warnings (STALE and FUTURE-DATED findings are
|
|
66
|
+
warnings) and 1 when it finds an error, so read the JSON report to decide.
|
|
67
|
+
Any other outcome means the checker could not run (exit 2 for a usage error
|
|
68
|
+
such as a missing bundle directory, a failed install, a missing command, or a
|
|
69
|
+
report that does not parse), and it fails the job at every stage, including
|
|
70
|
+
stage 1.
|
|
71
|
+
|
|
72
|
+
The stage 2 selection, as a `jq` filter over the `--json` report (any JSON
|
|
73
|
+
tool works; the report is `{ "findings": [{ "ruleId", "severity", "file",
|
|
74
|
+
"message" }], ... }`):
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
jq '[.findings[]
|
|
78
|
+
| select(.severity == "error"
|
|
79
|
+
or (.severity == "warning"
|
|
80
|
+
and (.ruleId == "sources-fresh"
|
|
81
|
+
or .ruleId == "sources-fresh-future")))]
|
|
82
|
+
| length' "$report"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
A result above 0 fails the job.
|
|
86
|
+
|
|
87
|
+
### Example: GitHub Actions
|
|
88
|
+
|
|
89
|
+
A reusable workflow with the runner and the stage as inputs and one matrix
|
|
90
|
+
entry per configured bundle:
|
|
91
|
+
|
|
92
|
+
```yaml
|
|
93
|
+
on:
|
|
94
|
+
workflow_call:
|
|
95
|
+
inputs:
|
|
96
|
+
runner:
|
|
97
|
+
type: string
|
|
98
|
+
required: true
|
|
99
|
+
stage:
|
|
100
|
+
type: string # warn | block | strict
|
|
101
|
+
required: true
|
|
102
|
+
|
|
103
|
+
jobs:
|
|
104
|
+
bundle-gate:
|
|
105
|
+
runs-on: ${{ inputs.runner }}
|
|
106
|
+
strategy:
|
|
107
|
+
fail-fast: false
|
|
108
|
+
matrix:
|
|
109
|
+
bundle:
|
|
110
|
+
# one entry per `knowledge` entry in .ai/workflow/manifest.json
|
|
111
|
+
- { path: docs/okf, repoRoot: . }
|
|
112
|
+
steps:
|
|
113
|
+
- uses: actions/checkout@<pinned-ref>
|
|
114
|
+
with:
|
|
115
|
+
fetch-depth: 0
|
|
116
|
+
- name: Bundle check
|
|
117
|
+
shell: bash
|
|
118
|
+
env:
|
|
119
|
+
OKF_KIT_VERSION: <pinned-version>
|
|
120
|
+
BUNDLE: ${{ matrix.bundle.path }}
|
|
121
|
+
REPO_ROOT: ${{ matrix.bundle.repoRoot }}
|
|
122
|
+
STAGE: ${{ inputs.stage }}
|
|
123
|
+
run: |
|
|
124
|
+
report="${RUNNER_TEMP:-${TMPDIR:-/tmp}}/okf-report.json"
|
|
125
|
+
strict=""
|
|
126
|
+
if [ "$STAGE" = "strict" ]; then strict="--strict"; fi
|
|
127
|
+
set +e
|
|
128
|
+
npx -y "okf-kit@$OKF_KIT_VERSION" check "$BUNDLE" \
|
|
129
|
+
--repo-root "$REPO_ROOT" --json $strict > "$report"
|
|
130
|
+
status=$?
|
|
131
|
+
set -e
|
|
132
|
+
if [ "$status" -ne 0 ] && [ "$status" -ne 1 ]; then
|
|
133
|
+
echo "bundle check could not run (exit $status)"; exit 2
|
|
134
|
+
fi
|
|
135
|
+
if ! jq -e '.findings | type == "array"' "$report" > /dev/null; then
|
|
136
|
+
echo "bundle check could not run (no parseable report)"; exit 2
|
|
137
|
+
fi
|
|
138
|
+
jq -r --arg b "$BUNDLE" '.findings[]
|
|
139
|
+
| "::\(.severity) file=\($b)/\(.file)::\(.ruleId): \(.message)"' \
|
|
140
|
+
"$report"
|
|
141
|
+
jq -r --arg b "$BUNDLE" '"### Bundle check: \($b)",
|
|
142
|
+
(.findings[] | "- \(.severity) \(.ruleId) \(.file): \(.message)")' \
|
|
143
|
+
"$report" >> "$GITHUB_STEP_SUMMARY"
|
|
144
|
+
case "$STAGE" in
|
|
145
|
+
warn) exit 0 ;;
|
|
146
|
+
block)
|
|
147
|
+
blocking=$(jq '[.findings[]
|
|
148
|
+
| select(.severity == "error"
|
|
149
|
+
or (.severity == "warning"
|
|
150
|
+
and (.ruleId == "sources-fresh"
|
|
151
|
+
or .ruleId == "sources-fresh-future")))]
|
|
152
|
+
| length' "$report")
|
|
153
|
+
if [ "$blocking" -gt 0 ]; then exit 1; fi ;;
|
|
154
|
+
strict) exit "$status" ;;
|
|
155
|
+
*) echo "unknown stage: $STAGE"; exit 2 ;;
|
|
156
|
+
esac
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The annotation command names match the checker's severities (`error`,
|
|
160
|
+
`warning`, `notice`), so each finding lands on its file in the change view.
|
|
161
|
+
The report goes to the runner's temporary directory, outside the checked-out
|
|
162
|
+
work tree.
|
|
163
|
+
|
|
164
|
+
### Pre-commit parity
|
|
165
|
+
|
|
166
|
+
Run `okf-kit check <bundle> --repo-root <repoRoot> --dirty-as-now` before
|
|
167
|
+
committing: it treats every uncommitted change as one virtual commit made now,
|
|
168
|
+
so the local run reports the same `sources-fresh` and `sources-fresh-future`
|
|
169
|
+
verdict CI will report once the commit lands. Without the flag a pre-commit
|
|
170
|
+
run judges an edited source by its last commit and can report clean while CI
|
|
171
|
+
reports STALE after the push. A hook loops over the same bundles as CI:
|
|
172
|
+
|
|
173
|
+
```sh
|
|
174
|
+
report="$(mktemp)"
|
|
175
|
+
okf-kit check docs/okf --repo-root . --dirty-as-now --json > "$report"
|
|
176
|
+
# apply the same stage decision as CI to "$report"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Write the report outside the work tree: under `--dirty-as-now` a report file
|
|
180
|
+
inside it is itself an uncommitted change and can mark a doc STALE whose
|
|
181
|
+
sources cover that directory.
|
|
182
|
+
|
|
183
|
+
Parity covers the verdict of those two rules, not the stage decision: apply
|
|
184
|
+
the same stage filter locally, and add `--strict` only when CI runs stage 3.
|
|
185
|
+
|
|
186
|
+
### What the gate proves
|
|
187
|
+
|
|
188
|
+
The gate proves that a doc was re-stamped after its sources changed, not that
|
|
189
|
+
its content is correct; review of the doc against its sources stays mandatory.
|
|
190
|
+
A re-stamp without re-verification passes the gate, and a doc-only prose edit
|
|
191
|
+
that leaves the sources untouched gives the staleness rules nothing to compare
|
|
192
|
+
against. The gate complements the hand-off check and the reviewer; it replaces
|
|
193
|
+
neither.
|
|
@@ -154,8 +154,13 @@ commits:
|
|
|
154
154
|
|
|
155
155
|
Follow [evidence-and-probes.md workflow step 6](evidence-and-probes.md#workflow)
|
|
156
156
|
for implementation evidence, verification, mutation probes, and replay. For
|
|
157
|
-
commit reporting, follow the installed implementer role prompt.
|
|
158
|
-
|
|
157
|
+
commit reporting, follow the installed implementer role prompt. A return that
|
|
158
|
+
reports an outward action (see AGENTS.md's Outward-facing actions rule) as
|
|
159
|
+
executed is invalid, whatever the task assignment said; a local commit on the
|
|
160
|
+
task branch is not an outward action. If you performed one anyway, report
|
|
161
|
+
it in your return (what, where, when); performing one is forbidden,
|
|
162
|
+
reporting it is mandatory. Return the selected contract's YAML envelope.
|
|
163
|
+
`result: killed` means the probe's test
|
|
159
164
|
command reacted to the mutant under the runner's pass predicate, or the
|
|
160
165
|
test pass predicate declared in the task assignment or probe plan when no
|
|
161
166
|
runner supplies a verdict; `survived` means it did not. `expectation: met`
|
|
@@ -180,7 +185,11 @@ the declared expected result, and label both derivations as manual.
|
|
|
180
185
|
|
|
181
186
|
The output shape remains the same for either selected contract. Compare the
|
|
182
187
|
delegated versioned records and producer evidence under Contract selection
|
|
183
|
-
above; a recommendation does not replace orchestrator acceptance.
|
|
188
|
+
above; a recommendation does not replace orchestrator acceptance. A return
|
|
189
|
+
that reports an outward action (see AGENTS.md's Outward-facing actions rule)
|
|
190
|
+
as executed is invalid; the reviewer never performs one. If you performed
|
|
191
|
+
one anyway, report it in your return (what, where, when); performing one is
|
|
192
|
+
forbidden, reporting it is mandatory.
|
|
184
193
|
```yaml
|
|
185
194
|
status: reviewed
|
|
186
195
|
role: reviewer
|
|
@@ -323,6 +332,32 @@ values. The copied criterion records retain `id`, `required`, `text`,
|
|
|
323
332
|
contract, preserve its original strings and the same 1:1 field mapping with
|
|
324
333
|
the transformation under Contract selection above.
|
|
325
334
|
|
|
335
|
+
Knowledge bundle docs in `relevant_docs`: for each configured knowledge bundle
|
|
336
|
+
(`knowledge` in `.ai/workflow/manifest.json`; default `docs/okf/`), every
|
|
337
|
+
bundle doc whose `sources` intersect the task's `allowed_changes` is listed in
|
|
338
|
+
`relevant_docs` with the bundle doc marker, written as `<doc path> (knowledge
|
|
339
|
+
bundle; sources: <intersecting sources>)`. The marker is the one annotation
|
|
340
|
+
that identifies a bundle doc entry; the task slicer writes it, and the
|
|
341
|
+
implementer and reviewer recognize bundle docs by it. Resolve each doc's
|
|
342
|
+
`sources` against its bundle's configured `repoRoot` and compare them with the
|
|
343
|
+
`allowed_changes` in that repository. A source intersects when
|
|
344
|
+
it names a path the task may change, a directory containing one, or a path
|
|
345
|
+
inside a directory the task may change; a source that is itself a directory
|
|
346
|
+
matches every path beneath it. Compute the
|
|
347
|
+
intersection with a bundle tool when one is available (for example `okf-kit
|
|
348
|
+
docs-for <bundle dir> <path>... --repo-root <repo root>`), or read each bundle
|
|
349
|
+
doc's `sources` frontmatter. A bundle tool is queried with concrete paths, so
|
|
350
|
+
first expand each directory or glob entry of `allowed_changes` to the tracked
|
|
351
|
+
files it covers (for example `git ls-files -- <entry>`); when reading the
|
|
352
|
+
frontmatter instead, match directory and glob entries against each source
|
|
353
|
+
directly. Each such doc is also listed in `allowed_changes`,
|
|
354
|
+
so the implementer can re-stamp it. When `forbidden_changes` cover such a doc,
|
|
355
|
+
the slicer leaves it out of `allowed_changes` and records an open question for
|
|
356
|
+
the orchestrator instead. For a bundle whose docs live in a different
|
|
357
|
+
repository than its sources (a workspace bundle), the re-stamp commit is one
|
|
358
|
+
in the bundle's repository within the same task. This reuses `relevant_docs`;
|
|
359
|
+
the contract shape is unchanged.
|
|
360
|
+
|
|
326
361
|
## Advisor output contract
|
|
327
362
|
|
|
328
363
|
```yaml
|
|
@@ -27,8 +27,9 @@ directory and the subagents.
|
|
|
27
27
|
If the task can proceed on reasonable assumptions, proceed without blocking.
|
|
28
28
|
2. **Discover (optional, read-only).** When the goal, the solution, or the
|
|
29
29
|
terrain is unclear, send the explorer subagent before planning. Have it
|
|
30
|
-
check for a curated knowledge bundle (
|
|
31
|
-
|
|
30
|
+
check for a curated knowledge bundle (each one configured via `knowledge`
|
|
31
|
+
in `.ai/workflow/manifest.json`; default `docs/okf/`, typically a
|
|
32
|
+
directory with an index) before mapping terrain by hand, treating any claims found
|
|
32
33
|
there as leads to verify, not as ground truth, and prefer a connected
|
|
33
34
|
semantic code-search tool over raw grep for orientation questions; when a
|
|
34
35
|
structural code-search tool is available, prefer it over text grep for
|
|
@@ -58,6 +59,11 @@ directory and the subagents.
|
|
|
58
59
|
the task will change, enumerate every file and doc site that references it
|
|
59
60
|
in `relevant_files` or `relevant_docs`, with an annotation for a site the
|
|
60
61
|
task will not edit.
|
|
62
|
+
Every configured knowledge bundle doc whose `sources` intersect the task's
|
|
63
|
+
`allowed_changes` goes into `relevant_docs` with the bundle doc marker and
|
|
64
|
+
into `allowed_changes`, as contracts.md defines, so the task that changes a
|
|
65
|
+
source re-verifies and re-stamps its doc itself, in the same commit as the
|
|
66
|
+
source change, or in a later commit of the same task.
|
|
61
67
|
5. **Validate tasks.** Check the slices are independently understandable, small
|
|
62
68
|
enough, testable, ordered correctly, and aligned with the goal. Fix the
|
|
63
69
|
slicing before any implementation starts. For an explicitly adopted v1 run,
|
|
@@ -152,6 +158,62 @@ directory and the subagents.
|
|
|
152
158
|
engine. Only the orchestrator can explicitly revise a baseline, recording
|
|
153
159
|
old/new revisions, affected IDs, authority and reason, invalidated evidence,
|
|
154
160
|
and verified rationale for carrying unchanged evidence forward. Record a baseline revision only when scope or the normative text of a criterion changes, including a change to what its verification checks; a wording precision that leaves the check itself unchanged is a `03-decisions.md` entry, not a revision: the orchestrator records it, states in that entry why no evidence is invalidated, and communicates the corrected wording in the next delegation.
|
|
161
|
+
After each implementer return, mechanically cross-check its self-report
|
|
162
|
+
against the outward-actions rule (see AGENTS.md's Outward-facing actions
|
|
163
|
+
section). The `commits` comparison starts from the round's task base,
|
|
164
|
+
which the orchestrator names in this round's assignment as the
|
|
165
|
+
implementer's `<base>` (the sha the task branch started from on the
|
|
166
|
+
task's first round, or the previous round's reviewed head on a later
|
|
167
|
+
round); the ref check starts from the task's first-round base, so a ref
|
|
168
|
+
at an earlier round's commit stays covered. Neither starts from the
|
|
169
|
+
run-base, whose range also holds earlier tasks and upstream work merged
|
|
170
|
+
after it. When handing a round over, record its task base and the remote
|
|
171
|
+
default branch's sha at that moment (for example `git rev-parse
|
|
172
|
+
<remote>/<default-branch>` right after `git fetch <remote>`, or the
|
|
173
|
+
host's equivalent). Compare `git rev-list --reverse
|
|
174
|
+
<task-base>..<task-branch>` (or the host's equivalent) against the
|
|
175
|
+
returned `commits` field. List the remote's refs (for example `git
|
|
176
|
+
ls-remote <remote>`, or the host's equivalent) and flag every branch or
|
|
177
|
+
tag whose sha, peeled for an annotated tag, lies in the
|
|
178
|
+
`<first-round-base>..<task-branch>` range and is not reachable from the
|
|
179
|
+
remote default branch's sha recorded at this round's handover (for
|
|
180
|
+
example, every sha that `git rev-list <task-branch> ^<first-round-base>
|
|
181
|
+
^<recorded-default-sha>` lists), unless the orchestrator moved that ref
|
|
182
|
+
to that sha itself (its own push, or a host-side merge it performed).
|
|
183
|
+
Reachability is judged from the recorded sha rather than the default
|
|
184
|
+
branch's current one, so a push of the task's commits to the default
|
|
185
|
+
branch is still flagged, while upstream work the task branch took in from
|
|
186
|
+
the recorded default branch is not. A round therefore takes in upstream
|
|
187
|
+
work only up to its recorded sha: a ref at a commit that reached the
|
|
188
|
+
default branch after the handover is flagged like one at the task's own
|
|
189
|
+
commits. Without a recorded sha, judge reachability from the first-round
|
|
190
|
+
base itself, which errs
|
|
191
|
+
toward a flag. Compare an existing task-branch ref
|
|
192
|
+
with the sha the orchestrator last pushed there, rather than treating the
|
|
193
|
+
ref's existence as a misfire; and confirm no pull request exists on the
|
|
194
|
+
task branch that the orchestrator did not open itself (for example `gh pr
|
|
195
|
+
list --head <branch>`, or the host's equivalent). A `commits` mismatch,
|
|
196
|
+
a pull request the orchestrator did not open, or a return that reports
|
|
197
|
+
an outward action as executed, is a misfire: do not fold it into run
|
|
198
|
+
state as evidence, and recover it under the subagent misfire rule. A
|
|
199
|
+
flagged ref is a signal to investigate, not a misfire by itself: before
|
|
200
|
+
treating it as one, the orchestrator establishes who moved the ref (for
|
|
201
|
+
example from the host's push or audit events, or by asking the
|
|
202
|
+
operator). When that cannot be established, it treats the ref as a
|
|
203
|
+
misfire and reports it to the operator. A ref a third party moved, or
|
|
204
|
+
one already recorded as an incident in an earlier round, is recorded
|
|
205
|
+
once in `03-decisions.md` and is not treated as a new finding again
|
|
206
|
+
while it stays at that sha; a later move of such a ref is investigated
|
|
207
|
+
like any other flagged ref. The ref check is a heuristic next to the
|
|
208
|
+
subagent's mandatory self-report, not a complete detector: for example,
|
|
209
|
+
it cannot see a deleted ref, a rewound default branch, a ref at an
|
|
210
|
+
already public sha, a ref at a commit a rebase dropped from the task
|
|
211
|
+
branch, or a pushed merge or squash of the task branch. When the check
|
|
212
|
+
finds an outward action was actually performed (a push, an opened pull
|
|
213
|
+
request) without authorization, that is more than a misfire to resume
|
|
214
|
+
past: the orchestrator informs the operator immediately, records the
|
|
215
|
+
incident in `03-decisions.md`, and lists it in `06-handoff.md`'s Sent /
|
|
216
|
+
Drafted Outward section as unauthorized.
|
|
155
217
|
7. **Delegate review.** Send the diff to the reviewer subagent, naming in the
|
|
156
218
|
briefing the base and head revision the diff was generated from. When tier
|
|
157
219
|
variants are installed, pick the reviewer tier (the installed
|
|
@@ -279,10 +341,15 @@ directory and the subagents.
|
|
|
279
341
|
subagent, if any) by the same complexity-and-risk judgment already used
|
|
280
342
|
for the implementer and reviewer tiers, defaulting to the unsuffixed
|
|
281
343
|
subagent (already effort `high`) when unsure.
|
|
282
|
-
9. **Hand off.**
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
344
|
+
9. **Hand off.** Bundle docs whose sources a task changes are listed in its
|
|
345
|
+
`relevant_docs` at slicing (step 4) and re-stamped by that task, so this
|
|
346
|
+
hand-off check is a safety net for sources the task list missed. Before
|
|
347
|
+
filling `06-handoff.md`, apply this optional
|
|
348
|
+
guidance: when the repo carries a curated knowledge bundle (each one
|
|
349
|
+
configured via `knowledge` in `.ai/workflow/manifest.json`; default
|
|
350
|
+
`docs/okf/`), check whether the change touches
|
|
351
|
+
paths any bundle doc claims as sources that no task re-stamped; if so,
|
|
352
|
+
update the affected docs
|
|
286
353
|
(re-verify and re-stamp) or record a follow-up task, and run the bundle
|
|
287
354
|
validator when one is available (for example `okf-kit check`). Repos
|
|
288
355
|
without a bundle are unaffected. Then fill `06-handoff.md` and report to the
|
|
@@ -361,8 +428,9 @@ categories disabled by effective configuration are reported as gaps. A missing,
|
|
|
361
428
|
extra, mismatched, or unresolved named result is a misfire; a reported failure
|
|
362
429
|
is an honest failure, not a misfire. `skip`, `acknowledged`, `limitation`, and
|
|
363
430
|
inconclusive results remain non-passes and cannot be silently accepted. When a
|
|
364
|
-
repository has
|
|
365
|
-
|
|
431
|
+
repository has a configured knowledge bundle (default `docs/okf/`, see
|
|
432
|
+
`knowledge` in `.ai/workflow/manifest.json`), include its bundle check in
|
|
433
|
+
every set regardless of which files changed. This is a documented convention, not an OW execution
|
|
366
434
|
engine or runtime schema validator. A quoted probe verdict is not a named result
|
|
367
435
|
of the verification set, so the set's missing-or-extra rule does not apply to it.
|
|
368
436
|
|
|
@@ -8,7 +8,15 @@ against its role's output contract, including an implementer return that
|
|
|
8
8
|
omits the `mutation_probes` field even though the task assignment named
|
|
9
9
|
mutation probes to run, or that omits the `commits` field even though the
|
|
10
10
|
task assignment asked for a commit, or that omits the `class_closure`
|
|
11
|
-
field on any round after the task's first
|
|
11
|
+
field on any round after the task's first, or that reports an outward
|
|
12
|
+
action (see AGENTS.md's Outward-facing actions rule) as executed, since a
|
|
13
|
+
task assignment never authorizes one. Performing an outward action is
|
|
14
|
+
forbidden, but reporting one that was actually performed is still
|
|
15
|
+
mandatory: recovering the return as a misfire (it is not evidence) does not
|
|
16
|
+
excuse the orchestrator from also treating the report itself as an
|
|
17
|
+
incident, informing the operator immediately, recording it in
|
|
18
|
+
`03-decisions.md`, and listing it in `06-handoff.md`'s Sent / Drafted
|
|
19
|
+
Outward section as unauthorized. When a subagent returns near-instantly
|
|
12
20
|
with no tool activity, treat that as a misfire signal rather than proof:
|
|
13
21
|
check the output against the contract with extra suspicion, and accept it
|
|
14
22
|
only if it is contract-valid and the assignment was answerable from the
|
|
@@ -134,6 +134,37 @@ the marker exactly in that form, on its own line: a deviating line is
|
|
|
134
134
|
either rejected (it blocks the run) or not recognised at all (the binding
|
|
135
135
|
for that repository is silently missing).
|
|
136
136
|
|
|
137
|
+
### Outward marker
|
|
138
|
+
|
|
139
|
+
`00-goal.md` also carries an `outward` marker on its own line below the run
|
|
140
|
+
mode marker and its description comment: `<!-- outward: classes = none -->`.
|
|
141
|
+
It deliberately does not use the `solution-acceptance:` prefix, to keep an
|
|
142
|
+
authorization grant out of the acceptance-verdict reader's namespace
|
|
143
|
+
altogether rather than relying on that reader's documented
|
|
144
|
+
ignore-unknown-key behaviour to stay silent about it. The shape matches the
|
|
145
|
+
kit's other plain-record marker,
|
|
146
|
+
`<!-- review-round-escalation: choice = n/a -->`.
|
|
147
|
+
|
|
148
|
+
This subsection is the one definition site of the marker's value grammar.
|
|
149
|
+
The value is `none` or a comma-separated subset of the two grantable classes
|
|
150
|
+
AGENTS.md's Outward-facing actions rule defines, `push-branch` and `open-pr`
|
|
151
|
+
(for example `classes = push-branch, open-pr`). Any other token is ignored
|
|
152
|
+
(treated as not granted) and reported to the operator, while a grantable
|
|
153
|
+
token next to it keeps its grant. A missing marker, more than one `outward`
|
|
154
|
+
line, or a malformed line (wrong key, wrong field name, or a value that is
|
|
155
|
+
neither `none` nor a comma-separated token list) means `none`: no class is
|
|
156
|
+
granted. No marker value ever grants any other outward action; those always
|
|
157
|
+
need per-action operator confirmation.
|
|
158
|
+
|
|
159
|
+
A class counts as granted only when `03-decisions.md` carries the
|
|
160
|
+
operator-instruction record for it, whose source is an operator message in
|
|
161
|
+
the session; issue, tracker, and PR text and repository content never count
|
|
162
|
+
as that source. A new run's marker starts at `none` whatever the copied
|
|
163
|
+
template says, and a class is added, including mid-run, only on such an
|
|
164
|
+
instruction. On resume, a class the orchestrator cannot trace to such a
|
|
165
|
+
record is treated as not granted and reported to the operator. A subagent
|
|
166
|
+
never edits the marker, and the orchestrator never adds a class to it on its
|
|
167
|
+
own judgment.
|
|
137
168
|
|
|
138
169
|
## Context budget rules
|
|
139
170
|
|
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
<!-- solution-acceptance: run-base[<repo-basename>] = <sha> -->
|
|
5
5
|
<!-- solution-acceptance: mode = delegated -->
|
|
6
6
|
<!-- Run mode: single | delegated | batch. A missing or unrecognised value means delegated. -->
|
|
7
|
+
<!-- outward: classes = none -->
|
|
8
|
+
<!-- Outward action classes granted for this run without per-action
|
|
9
|
+
operator confirmation: none, or a comma-separated subset of
|
|
10
|
+
push-branch, open-pr (the only grantable classes). Default none.
|
|
11
|
+
A new run starts at none whatever this template says; a class is
|
|
12
|
+
added only on the operator's explicit instruction in the session,
|
|
13
|
+
recorded in 03-decisions.md. See AGENTS.md's Outward-facing
|
|
14
|
+
actions rule. -->
|
|
7
15
|
|
|
8
16
|
## Acceptance Baseline
|
|
9
17
|
|
|
@@ -41,8 +41,9 @@ acceptance_criteria:
|
|
|
41
41
|
digest recorded in the briefing is what carries the orchestrator's approval
|
|
42
42
|
of the resolved argv to the implementer and reviewer. The
|
|
43
43
|
orchestrator approves effective config/scripts before any acquisition or
|
|
44
|
-
execution; include an ordered
|
|
45
|
-
|
|
44
|
+
execution; include an ordered bundle check for each configured knowledge
|
|
45
|
+
bundle (`knowledge` in `.ai/workflow/manifest.json`; default `docs/okf/`)
|
|
46
|
+
whenever one exists. -->
|
|
46
47
|
|
|
47
48
|
**Relevant Files / Areas**
|
|
48
49
|
|
|
@@ -51,6 +52,9 @@ exists. -->
|
|
|
51
52
|
**Relevant Docs**
|
|
52
53
|
|
|
53
54
|
- <!-- doc, ADR, or run file the task relies on, or none -->
|
|
55
|
+
- <!-- each knowledge bundle doc whose sources intersect the allowed changes,
|
|
56
|
+
marked `<doc path> (knowledge bundle; sources: <intersecting sources>)`
|
|
57
|
+
(contracts.md defines the marker) -->
|
|
54
58
|
|
|
55
59
|
**Acceptance Criteria**
|
|
56
60
|
|
|
@@ -27,11 +27,31 @@
|
|
|
27
27
|
## Knowledge Bundle
|
|
28
28
|
|
|
29
29
|
<!-- Optional: only applies when the repo carries a curated knowledge bundle
|
|
30
|
-
(
|
|
31
|
-
|
|
30
|
+
(each one configured via `knowledge` in `.ai/workflow/manifest.json`;
|
|
31
|
+
default `docs/okf/`). This is the safety net for bundle docs no task
|
|
32
|
+
re-stamped: a task that changes a doc's sources re-stamps it itself.
|
|
33
|
+
Outcome: updated | not affected | follow-up filed. -->
|
|
32
34
|
|
|
33
35
|
- <!-- outcome and brief note, or omit this section when the repo carries no bundle -->
|
|
34
36
|
|
|
37
|
+
## Documentation Impact
|
|
38
|
+
|
|
39
|
+
<!-- Human-facing documentation (README, ADRs, architecture docs, end-user
|
|
40
|
+
docs) affected by this run. Exactly one of: none (<reason>) |
|
|
41
|
+
updated: <paths> | follow-up: <task>. -->
|
|
42
|
+
|
|
43
|
+
- <!-- none (<reason>) | updated: <paths> | follow-up: <task> -->
|
|
44
|
+
|
|
45
|
+
## Sent / Drafted Outward
|
|
46
|
+
|
|
47
|
+
<!-- Optional: only applies when this run performed or drafted an outward
|
|
48
|
+
action (push, pull request, ticket comment/transition/close, release,
|
|
49
|
+
publish, message). Omit this section when nothing was sent or drafted. -->
|
|
50
|
+
|
|
51
|
+
- <!-- action class, what was sent (with confirmation basis), what stayed a
|
|
52
|
+
draft in the run directory and why, or an action performed without
|
|
53
|
+
authorization (unauthorized) -->
|
|
54
|
+
|
|
35
55
|
## Follow-Ups
|
|
36
56
|
|
|
37
57
|
- <!-- next steps or none -->
|
package/dist/cli.js
CHANGED
|
@@ -887,6 +887,12 @@ function printTargetDetail(target, operatorVersion) {
|
|
|
887
887
|
for (const gap of target.routingComparisonGaps ?? []) {
|
|
888
888
|
console.log(` Routing comparison incomplete: ${gap}`);
|
|
889
889
|
}
|
|
890
|
+
// Knowledge-bundle warnings print for every status: they never change
|
|
891
|
+
// the status line, so without this line a plain `doctor` run would show
|
|
892
|
+
// `clean` and hide them (they are otherwise only in `--json`).
|
|
893
|
+
for (const warning of target.knowledgeWarnings ?? []) {
|
|
894
|
+
console.log(` knowledge: ${warning}`);
|
|
895
|
+
}
|
|
890
896
|
const showsVersionLagDetail = (target.status === "version-lag" ||
|
|
891
897
|
((target.status === "divergent" || target.status === "drift") &&
|
|
892
898
|
target.versionLag)) &&
|
package/dist/doctor.d.ts
CHANGED
|
@@ -44,6 +44,17 @@ export interface TargetReport {
|
|
|
44
44
|
driftFiles: string[] | null;
|
|
45
45
|
/** Selected legacy opencode leaves that cannot be compared offline. */
|
|
46
46
|
routingComparisonGaps?: string[];
|
|
47
|
+
/**
|
|
48
|
+
* Knowledge-bundle warnings against this target's own filesystem: a
|
|
49
|
+
* malformed `knowledge` entry dropped on read (index and reason), a
|
|
50
|
+
* configured `path` or `repoRoot` that is not a directory under the
|
|
51
|
+
* target's top level, or a non-empty `knowledge` list that omits an
|
|
52
|
+
* existing `docs/okf/` at the target's root. Omitted when there is none.
|
|
53
|
+
* Printed as `knowledge:` detail lines in the human output. These
|
|
54
|
+
* warnings never change `status` or the doctor exit code -- they surface
|
|
55
|
+
* a documentation-locator drift, not a kit-file install drift.
|
|
56
|
+
*/
|
|
57
|
+
knowledgeWarnings?: string[];
|
|
47
58
|
/** Human-output-only: the repo's own profile, or null when unknown. */
|
|
48
59
|
repoProfile: Profile | null;
|
|
49
60
|
/** Human-output-only: the operator default profile, for the comparison line. */
|
|
@@ -87,6 +98,8 @@ export interface TargetReportJson {
|
|
|
87
98
|
driftFiles: string[] | null;
|
|
88
99
|
/** Selected legacy opencode leaves that cannot be compared offline. */
|
|
89
100
|
routingComparisonGaps?: string[];
|
|
101
|
+
/** See {@link TargetReport.knowledgeWarnings}. */
|
|
102
|
+
knowledgeWarnings?: string[];
|
|
90
103
|
versionLag: boolean;
|
|
91
104
|
reason: string | null;
|
|
92
105
|
}
|
package/dist/doctor.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { PACKAGE_VERSION } from "./assets.js";
|
|
5
|
-
import { MANIFEST_PATH, readInstalledManifest } from "./init.js";
|
|
5
|
+
import { MANIFEST_PATH, knowledgeEntryProblems, readInstalledManifest, } from "./init.js";
|
|
6
6
|
import { DEFAULT_MODELS, rolesForProfile } from "./models.js";
|
|
7
7
|
import { compareRoutingState } from "./routing-state.js";
|
|
8
8
|
import { OPERATOR_MANIFEST_FILENAME, operatorManifestState, updateOperatorManifest, } from "./operator-manifest.js";
|
|
@@ -17,6 +17,9 @@ export function targetReportToJson(report) {
|
|
|
17
17
|
...(report.routingComparisonGaps?.length
|
|
18
18
|
? { routingComparisonGaps: report.routingComparisonGaps }
|
|
19
19
|
: {}),
|
|
20
|
+
...(report.knowledgeWarnings?.length
|
|
21
|
+
? { knowledgeWarnings: report.knowledgeWarnings }
|
|
22
|
+
: {}),
|
|
20
23
|
versionLag: report.versionLag,
|
|
21
24
|
reason: report.reason,
|
|
22
25
|
};
|
|
@@ -111,6 +114,61 @@ function computeDriftFiles(targetPath, manifest) {
|
|
|
111
114
|
}
|
|
112
115
|
return drifted;
|
|
113
116
|
}
|
|
117
|
+
/** The kit-wide implicit default knowledge-bundle location every site falls
|
|
118
|
+
* back to when a target configures no `knowledge` list at all. */
|
|
119
|
+
const DEFAULT_KNOWLEDGE_PATH = "docs/okf";
|
|
120
|
+
/** The raw `knowledge` value of a target's manifest file, before
|
|
121
|
+
* `readInstalledManifest` drops invalid entries; `undefined` when the field
|
|
122
|
+
* is absent or the file cannot be re-read. */
|
|
123
|
+
function readRawKnowledge(targetPath) {
|
|
124
|
+
try {
|
|
125
|
+
const parsed = JSON.parse(readFileSync(join(targetPath, MANIFEST_PATH), "utf8"));
|
|
126
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
127
|
+
return undefined;
|
|
128
|
+
return parsed.knowledge;
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function isDirectoryAt(path) {
|
|
135
|
+
const stat = statOrClassify(path);
|
|
136
|
+
return stat.kind === "ok" && stat.stat.isDirectory();
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Checks a target's configured `knowledge` list against its own filesystem.
|
|
140
|
+
* Each raw entry `readInstalledManifest` dropped as malformed is reported
|
|
141
|
+
* with its index and reason. For each kept entry, `path` and `repoRoot` are
|
|
142
|
+
* resolved against the target's top level independently (the manifest's
|
|
143
|
+
* model; `path` is not nested under `repoRoot`) and each one that is not a
|
|
144
|
+
* directory is reported by name. A non-empty list whose normalised `path`s
|
|
145
|
+
* omit an existing `docs/okf/` at the target's root (the implicit default
|
|
146
|
+
* every kit site assumes absent a `knowledge` field) is reported once.
|
|
147
|
+
* Absent or empty `knowledge` is today's default behaviour and never warns
|
|
148
|
+
* about `docs/okf/`: a repo that never configured `knowledge` is not warned
|
|
149
|
+
* about lacking an entry for its own default.
|
|
150
|
+
*/
|
|
151
|
+
function computeKnowledgeWarnings(targetPath, manifest) {
|
|
152
|
+
const knowledge = manifest.knowledge ?? [];
|
|
153
|
+
const warnings = knowledgeEntryProblems(readRawKnowledge(targetPath));
|
|
154
|
+
for (const bundle of knowledge) {
|
|
155
|
+
if (!isDirectoryAt(join(targetPath, bundle.path))) {
|
|
156
|
+
warnings.push(`missing configured knowledge path: ${bundle.path}`);
|
|
157
|
+
}
|
|
158
|
+
if (bundle.repoRoot !== "." &&
|
|
159
|
+
!isDirectoryAt(join(targetPath, bundle.repoRoot))) {
|
|
160
|
+
warnings.push(`missing configured knowledge repoRoot: ${bundle.repoRoot}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (knowledge.length > 0) {
|
|
164
|
+
const docsOkfExists = isDirectoryAt(join(targetPath, DEFAULT_KNOWLEDGE_PATH));
|
|
165
|
+
const docsOkfConfigured = knowledge.some((bundle) => bundle.path === DEFAULT_KNOWLEDGE_PATH);
|
|
166
|
+
if (docsOkfExists && !docsOkfConfigured) {
|
|
167
|
+
warnings.push(`${DEFAULT_KNOWLEDGE_PATH}/ exists but is not in the configured knowledge list`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return warnings;
|
|
171
|
+
}
|
|
114
172
|
function baseReport(target, operator, status, reason) {
|
|
115
173
|
return {
|
|
116
174
|
path: target.path,
|
|
@@ -210,6 +268,7 @@ export function inspectTarget(target, operator, kitVersion) {
|
|
|
210
268
|
const versionLag = hasPin
|
|
211
269
|
? manifest.pin !== manifest.version
|
|
212
270
|
: manifest.version !== kitVersion;
|
|
271
|
+
const knowledgeWarnings = computeKnowledgeWarnings(target.path, manifest);
|
|
213
272
|
let status;
|
|
214
273
|
if (driftFiles.length > 0) {
|
|
215
274
|
status = "drift";
|
|
@@ -236,6 +295,7 @@ export function inspectTarget(target, operator, kitVersion) {
|
|
|
236
295
|
...(routingComparison.gaps.length > 0
|
|
237
296
|
? { routingComparisonGaps: routingComparison.gaps }
|
|
238
297
|
: {}),
|
|
298
|
+
...(knowledgeWarnings.length > 0 ? { knowledgeWarnings } : {}),
|
|
239
299
|
repoProfile: manifest.profile,
|
|
240
300
|
operatorProfile: operator.defaults.profile,
|
|
241
301
|
repoTiers: manifest.tiers,
|