Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion factory/coordinator.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ return if context_attempt.caught {
}
```

If the program returns `factory_status`, set terminal state to `failed`, record the fixed blocker `Phase 1 guide-context inspection failed.`, set `stop_model_phases = true`, skip every remaining model phase, and continue to atomic report creation. Otherwise the program must return the successful shell result object unchanged; do not parse, project, or reshape it inside compose. Read authority, role, schema, persona, representative-guide, and target-artifact contents only from its `.files` object. If Kit spills this large result, consume it only through the repository spill reader. First execute exactly `bash factory/scripts/read-guide-context-spill.sh <artifact> index` in a caught boundary, where `<artifact>` is the exact path returned by Kit. Consume listed files with caught commands shaped exactly `bash factory/scripts/read-guide-context-spill.sh <artifact> read <index> <offset>`. Files may be consumed in any order or interleaved. For each index, start each file at offset 0 and use only that file's returned next_offset until it reaches done=true; never use one file's offset for another index. Every listed file must reach done=true before Phase 2. Never construct jq, sed, Python, or other free-form spill commands; never read a spill with shell redirection or a generic file command. A rejected spill, missing index/chunk, changed path/index/offset, or incomplete file consumption selects failed; never inspect repository files directly or run another Phase 1 file-discovery tool. Resolve the persona only after reading every available definition under `doctrine/personas/`: default to `it-admin`; override it only when the issue confidently names an available repository persona. Pass the selected `doctrine/personas/<persona>.md` file to every downstream agent and reviewer. Resolve catalog presence only from the `.catalog` object returned by the initial command; never inspect `/input/catalog.json` directly. Preserve tenanted remote and `speakeasy_add_server` catalog/custom-remote doctrine. Skipped, malformed, stale, or ambiguous lookup means unknown, never absence; preserve both safe setup paths and record a research limitation rather than asking the operator to repeat the lookup.
If the program returns `factory_status`, set terminal state to `failed`, record the fixed blocker `Phase 1 guide-context inspection failed.`, set `stop_model_phases = true`, skip every remaining model phase, and continue to atomic report creation. Otherwise the program must return the successful shell result object unchanged; do not parse, project, or reshape it inside compose. Its stdout is a bounded manifest of approved repository paths and character counts and must never return raw context file contents. Accept only exact top-level keys `slug` and `files`, the resolved slug, and a sorted unique array of 1 through 40 objects with exact keys `path` and `characters`; paths must match the repository context allowlist and character counts must be nonnegative integers. Manifest validation must require every mandatory authority, default-persona, role, and output-schema path: the four fixed doctrine files, `schema/guide.v1.schema.json`, `doctrine/personas/it-admin.md`, the technical-research, writer, fidelity, and review role files, and the research-status, review-findings, and run-report schema files. Any malformed or incomplete manifest selects `failed`.

The manifest authorizes later reads; it is not a requirement to preload all content. The coordinator does not read every context file before Phase 2 and must never run another Phase 1 file-discovery tool. Resolve the persona from issue evidence and available persona paths in the manifest: default to `it-admin`; override it only when the issue confidently names an available repository persona. Pass the selected `doctrine/personas/<persona>.md` path to every downstream agent and reviewer. Include an explicit phase-specific list of manifest-member relative paths in every child prompt. Each downstream agent must resolve each listed path as `/workspace/<path>` and read only those paths using bounded targeted reads for the authority, role, schema, persona, representative-guide, and target-artifact files needed by that phase; prohibit every other repository read and all file discovery. Resolve catalog presence only from the `.catalog` object returned by the initial command; never inspect `/input/catalog.json` directly. Preserve tenanted remote and `speakeasy_add_server` catalog/custom-remote doctrine. Skipped, malformed, stale, or ambiguous lookup means unknown, never absence; preserve both safe setup paths and record a research limitation rather than asking the operator to repeat the lookup.

## Phase 2 — research and scope gate

Expand Down
48 changes: 38 additions & 10 deletions factory/scripts/inspect-guide-context.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,41 @@ ROOT="$(cd "$ROOT" && pwd -P)"
source "$SCRIPT_ROOT/factory/scripts/lib.sh"
[[ $# -eq 1 ]] || die "usage: ${0##*/} <slug>"
slug=$1
[[ "$slug" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]] || die "invalid guide slug"
[[ "$slug" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ && ${#slug} -le 96 ]] || die "invalid guide slug"

paths=''
sorted=''
files=''
manifest=''
next=''
guide_dirs=''
cleanup() {
for path in "$paths" "$sorted" "$files" "$next" "$guide_dirs"; do
for path in "$paths" "$sorted" "$manifest" "$next" "$guide_dirs"; do
[[ -z "$path" ]] || rm -f "$path"
done
}
trap cleanup EXIT
paths="$(mktemp "${TMPDIR:-/tmp}/factory-context-paths.XXXXXX")"
sorted="$(mktemp "${TMPDIR:-/tmp}/factory-context-sorted.XXXXXX")"
files="$(mktemp "${TMPDIR:-/tmp}/factory-context-files.XXXXXX")"
manifest="$(mktemp "${TMPDIR:-/tmp}/factory-context-manifest.XXXXXX")"
next="$(mktemp "${TMPDIR:-/tmp}/factory-context-next.XXXXXX")"
guide_dirs="$(mktemp "${TMPDIR:-/tmp}/factory-context-guides.XXXXXX")"
printf '{}\n' >"$files"
printf '[]\n' >"$manifest"

printf '%s\n' \
doctrine/constitution.md doctrine/shared.md doctrine/glossary.md \
doctrine/speakeasy-setup.md schema/guide.v1.schema.json >>"$paths"
for directory in doctrine/personas doctrine/roles factory/schemas; do
resolved_directory="$(realpath "$ROOT/$directory" 2>/dev/null)" \
|| die "invalid guide context directory"
[[ -d "$ROOT/$directory" && ! -L "$ROOT/$directory" \
&& "$resolved_directory" == "$ROOT/$directory" ]] || die "invalid guide context directory"
find "$ROOT/$directory" -maxdepth 1 -type f | while IFS= read -r path; do
printf '%s\n' "${path#"$ROOT/"}"
done >>"$paths"
done
resolved_guides="$(realpath "$ROOT/guides" 2>/dev/null)" || die "guide discovery failed"
[[ -d "$ROOT/guides" && ! -L "$ROOT/guides" && "$resolved_guides" == "$ROOT/guides" ]] \
|| die "guide discovery failed"
target="$ROOT/guides/$slug"
if [[ -e "$target" || -L "$target" ]]; then
[[ -d "$target" && ! -L "$target" ]] || die "invalid target guide directory"
Expand Down Expand Up @@ -67,12 +74,33 @@ while IFS= read -r guide; do
(( representatives == 2 )) && break
done <"$guide_dirs"

for required in doctrine/personas/it-admin.md doctrine/roles/technical-research.md \
doctrine/roles/writer.md doctrine/roles/fidelity.md doctrine/roles/review.md \
factory/schemas/research-status.schema.json factory/schemas/review-findings.schema.json \
factory/schemas/run-report.schema.json; do
resolved_required="$(realpath "$ROOT/$required" 2>/dev/null)" \
|| die "invalid guide context file"
[[ -f "$ROOT/$required" && ! -L "$ROOT/$required" \
&& "$resolved_required" == "$ROOT/$required" ]] || die "invalid guide context file"
done

sort -u "$paths" >"$sorted"
count="$(wc -l <"$sorted" | tr -d ' ')"
[[ "$count" =~ ^[1-9][0-9]*$ && "$count" -le 40 ]] || die "invalid guide context manifest"
while IFS= read -r path; do
[[ -f "$ROOT/$path" && ! -L "$ROOT/$path" ]] || die "invalid guide context file"
jq --arg path "$path" --rawfile content "$ROOT/$path" '. + {($path):$content}' \
"$files" >"$next"
mv "$next" "$files"
[[ ${#path} -le 120 ]] || die "invalid guide context manifest"
[[ "$path" =~ ^(doctrine/(constitution|shared|glossary|speakeasy-setup)\.md|doctrine/(personas|roles)/[A-Za-z0-9._-]+\.md|factory/schemas/[A-Za-z0-9._-]+\.json|schema/guide\.v1\.schema\.json|guides/[a-z0-9]+(-[a-z0-9]+)*/(research\.md|meta\.yaml|external\.md|speakeasy\.md))$ ]] \
|| die "invalid guide context manifest"
resolved_path="$(realpath "$ROOT/$path" 2>/dev/null)" || die "invalid guide context file"
[[ -f "$ROOT/$path" && ! -L "$ROOT/$path" && "$resolved_path" == "$ROOT/$path" ]] \
|| die "invalid guide context file"
characters="$(jq -Rs 'length' <"$ROOT/$path")" || die "invalid guide context file"
jq --arg path "$path" --argjson characters "$characters" \
'. + [{path:$path,characters:$characters}]' "$manifest" >"$next"
mv "$next" "$manifest"
done <"$sorted"

jq -n --arg slug "$slug" --slurpfile files "$files" '{slug:$slug,files:$files[0]}'
jq -cn --arg slug "$slug" --slurpfile files "$manifest" \
'{slug:$slug,files:$files[0]}' >"$next"
(( $(wc -c <"$next") <= 7000 )) || die "invalid guide context manifest"
cat "$next" || die "guide context manifest output failed"
120 changes: 0 additions & 120 deletions factory/scripts/read-guide-context-spill.sh

This file was deleted.

23 changes: 12 additions & 11 deletions factory/tests/test-coordinator.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,22 @@ for phrase in \
'bash factory/scripts/inspect-guide-context.sh <slug>' \
'return the successful shell result object unchanged' \
'do not parse, project, or reshape it inside compose' \
'bounded manifest of approved repository paths and character counts' \
'require every mandatory authority, default-persona, role, and output-schema path' \
'read only those paths using bounded targeted reads' \
'Include an explicit phase-specific list of manifest-member relative paths in every child prompt' \
"resolve each listed path as \`/workspace/<path>\`" \
'prohibit every other repository read' \
'never return raw context file contents' \
'bash factory/scripts/inspect-guide-artifacts.sh <slug> research' \
'bash factory/scripts/inspect-guide-artifacts.sh <slug> writer' \
'bash factory/scripts/inspect-guide-artifacts.sh <slug> revision' \
'Do not construct ad hoc artifact-validation commands' \
"Accept only the exact keys \`slug\`, \`stage\`, and \`artifacts\`" \
"Research accepts exactly \`[\"meta.yaml\",\"research.md\"]\` or the full four-file array" \
'Writer and revision require the full four-file array' \
'never inspect repository files directly or run another Phase 1 file-discovery tool' \
'bash factory/scripts/read-guide-context-spill.sh <artifact> index' \
'bash factory/scripts/read-guide-context-spill.sh <artifact> read <index> <offset>' \
'Files may be consumed in any order or interleaved' \
'start each file at offset 0 and use only that file' \
'Every listed file must reach done=true before Phase 2' \
'Never construct jq, sed, Python, or other free-form spill commands' \
'incomplete file consumption selects failed' \
'never run another Phase 1 file-discovery tool' \
'coordinator does not read every context file before Phase 2' \
"catalog presence only from the \`.catalog\` object returned by the initial command" \
"never inspect \`/input/catalog.json\` directly" \
'openai/gpt-5.6-sol' \
Expand Down Expand Up @@ -72,8 +73,8 @@ for phrase in \
grep -Fq "$phrase" "$CONTRACT" || fail "missing contract: $phrase"
done

if grep -Fq 'in index order' "$CONTRACT"; then
fail 'guide-context spill contract still requires global index order'
if grep -Eq 'read-guide-context-spill|next_offset|done=true|spill consumption' "$CONTRACT"; then
fail 'coordinator still requires model-managed guide-context spill consumption'
fi

context_boundary=$(cat <<'RUNLET'
Expand Down Expand Up @@ -162,7 +163,7 @@ for example in \
done

research_line="$(grep -n 'technical-research subagent' "$CONTRACT" | head -1 | cut -d: -f1)"
persona_line="$(grep -n 'Resolve the persona only after' "$CONTRACT" | head -1 | cut -d: -f1)"
persona_line="$(grep -n 'Resolve the persona from issue evidence' "$CONTRACT" | head -1 | cut -d: -f1)"
[[ -n "$persona_line" && "$persona_line" -lt "$research_line" ]] || fail "persona resolution must precede subagents"

temp_line="$(grep -n 'temporary report' "$CONTRACT" | tail -1 | cut -d: -f1)"
Expand Down
Loading
Loading