okstra 0.201.3 → 0.202.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/dist/commands/lifecycle/setup.mjs +15 -0
- package/dist/commands/lifecycle/setup.mjs.map +1 -1
- package/dist/lib/citation-guidance.d.mts +21 -0
- package/dist/lib/citation-guidance.mjs +79 -0
- package/dist/lib/citation-guidance.mjs.map +1 -0
- package/docs/architecture/storage-model.md +4 -0
- package/docs/architecture.md +1 -1
- package/docs/cli.md +2 -2
- package/docs/for-ai/skills/okstra-manager.md +21 -4
- package/docs/for-ai/skills/okstra-setup.md +9 -0
- package/docs/project-structure-overview.md +4 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/prompts/launch.template.md +1 -1
- package/runtime/prompts/lead/okstra-lead-contract.md +2 -2
- package/runtime/prompts/profiles/_implementation-verifier.md +1 -1
- package/runtime/prompts/profiles/implementation-planning.md +2 -0
- package/runtime/python/okstra_ctl/convergence_critic_prompt.py +4 -6
- package/runtime/python/okstra_ctl/convergence_provenance.py +75 -18
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +21 -21
- package/runtime/python/okstra_ctl/manager_cli.py +85 -12
- package/runtime/python/okstra_ctl/manager_launch.py +40 -18
- package/runtime/python/okstra_ctl/manager_paths.py +8 -0
- package/runtime/python/okstra_ctl/manager_split.py +474 -0
- package/runtime/python/okstra_ctl/manager_store.py +121 -18
- package/runtime/python/okstra_ctl/manager_sync.py +33 -15
- package/runtime/python/okstra_ctl/manager_view.py +216 -0
- package/runtime/python/okstra_ctl/plan_items_cli.py +6 -1
- package/runtime/python/okstra_ctl/qa_commands.py +15 -0
- package/runtime/python/okstra_ctl/report_finalize.py +13 -6
- package/runtime/python/okstra_ctl/report_html/view_models/final_verification.py +2 -21
- package/runtime/python/okstra_ctl/report_html/visualizations.py +0 -5
- package/runtime/python/okstra_ctl/verification_target.py +13 -2
- package/runtime/skills/okstra-manager/SKILL.md +53 -4
- package/runtime/skills/okstra-run/SKILL.md +1 -1
- package/runtime/skills/okstra-setup/SKILL.md +9 -0
- package/runtime/templates/manager/view.template.html +108 -0
- package/runtime/templates/reports/html/i18n/en.json +2 -0
- package/runtime/templates/reports/html/i18n/ko.json +2 -0
- package/runtime/templates/reports/html/tasks/final-verification.template.html +2 -2
- package/runtime/validators/validate-brief.py +7 -2
|
@@ -2,6 +2,7 @@ import { promises as fs } from "node:fs";
|
|
|
2
2
|
import { createInterface } from "node:readline";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join, resolve as resolvePath } from "node:path";
|
|
5
|
+
import { ensureCitationGuidance } from "../../lib/citation-guidance.mjs";
|
|
5
6
|
import { buildPythonpath, resolvePaths } from "../../lib/paths.mjs";
|
|
6
7
|
import { errorCode, errorMessage, fileExists, runProcess } from "../../lib/proc.mjs";
|
|
7
8
|
const USAGE = `okstra setup — register the current project with okstra
|
|
@@ -20,6 +21,9 @@ Usage:
|
|
|
20
21
|
on the command line
|
|
21
22
|
|
|
22
23
|
Behavior:
|
|
24
|
+
- If <PROJECT_ROOT>/CLAUDE.md or AGENTS.md exists, an okstra-managed block is
|
|
25
|
+
appended (or refreshed) telling agents not to cite okstra-internal ids and
|
|
26
|
+
.okstra/ paths outside okstra reports. Neither file is created.
|
|
23
27
|
- If project.json already exists, the projectId must match (okstra refuses
|
|
24
28
|
to silently rename a project). Delete the file manually if you really
|
|
25
29
|
want to change projectId.
|
|
@@ -229,11 +233,22 @@ export async function run(args) {
|
|
|
229
233
|
process.stderr.write(`warning: failed to provision .claude/settings.local.json symlink — ` +
|
|
230
234
|
`host Claude Code sessions in this project may need to add wrapper permissions manually. (${errorMessage(err)})\n`);
|
|
231
235
|
}
|
|
236
|
+
// 프로젝트에 host 지침 문서(CLAUDE.md / AGENTS.md)가 이미 있으면, okstra 산출물
|
|
237
|
+
// 인용 규율 블록을 그 끝에 심는다. 없는 파일은 만들지 않는다.
|
|
238
|
+
let citationGuidance = [];
|
|
239
|
+
try {
|
|
240
|
+
citationGuidance = await ensureCitationGuidance(projectRoot);
|
|
241
|
+
}
|
|
242
|
+
catch (err) {
|
|
243
|
+
process.stderr.write(`warning: failed to update okstra citation guidance in CLAUDE.md / AGENTS.md — ` +
|
|
244
|
+
`agents in this project may quote okstra-internal ids in non-okstra writing. (${errorMessage(err)})\n`);
|
|
245
|
+
}
|
|
232
246
|
process.stdout.write(JSON.stringify({
|
|
233
247
|
ok: true,
|
|
234
248
|
...result,
|
|
235
249
|
projectJsonPath,
|
|
236
250
|
settingsLocalJson: settingsSymlink,
|
|
251
|
+
citationGuidance,
|
|
237
252
|
}, null, 2) + "\n");
|
|
238
253
|
return 0;
|
|
239
254
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.mjs","sourceRoot":"","sources":["../../../src/commands/lifecycle/setup.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,WAAW,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGrF,MAAM,KAAK,GAAG
|
|
1
|
+
{"version":3,"file":"setup.mjs","sourceRoot":"","sources":["../../../src/commands/lifecycle/setup.mts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const GUIDANCE_BEGIN = "<!-- okstra:citation-guidance:begin -->";
|
|
2
|
+
export declare const GUIDANCE_END = "<!-- okstra:citation-guidance:end -->";
|
|
3
|
+
/** 관리 대상 host 지침 문서. 존재하는 것만 갱신한다. */
|
|
4
|
+
export declare const GUIDANCE_TARGET_FILES: readonly ["CLAUDE.md", "AGENTS.md"];
|
|
5
|
+
/** 마커로 감싼 블록 전문. 앞뒤 개행은 붙이는 쪽에서 관리한다. */
|
|
6
|
+
export declare function renderGuidanceBlock(): string;
|
|
7
|
+
/**
|
|
8
|
+
* 문서 본문에 블록을 반영한 결과를 돌려준다. 기존 블록이 있으면 그 자리에서
|
|
9
|
+
* 교체하고(문안이 바뀌어도 사용자가 쓴 앞부분은 그대로), 없으면 문서 끝에
|
|
10
|
+
* 붙인다. 내용이 이미 같으면 `changed: false` 로 파일을 건드리지 않는다.
|
|
11
|
+
*/
|
|
12
|
+
export declare function applyGuidance(content: string): {
|
|
13
|
+
content: string;
|
|
14
|
+
changed: boolean;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* 프로젝트 루트의 host 지침 문서를 갱신하고, 실제로 쓴 파일 경로를 돌려준다.
|
|
18
|
+
* 없는 파일은 건너뛴다. `AGENTS.md` 가 `CLAUDE.md` 심링크인 흔한 배치에서는
|
|
19
|
+
* 같은 실파일을 두 번 쓰지 않도록 realpath 로 중복을 제거한다.
|
|
20
|
+
*/
|
|
21
|
+
export declare function ensureCitationGuidance(projectRoot: string): Promise<string[]>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// okstra 산출물 인용 규율 블록을 대상 프로젝트의 host 지침 문서에 심는다.
|
|
2
|
+
//
|
|
3
|
+
// 배경: okstra 리포트는 `.okstra/` 안의 독자를 전제로 내부 식별자(리포트 절
|
|
4
|
+
// 번호, `C-001` 같은 clarification id, run/stage id, `.okstra/...` 경로)를
|
|
5
|
+
// 쓴다. 에이전트가 같은 식별자를 리포트 밖 글(대화 답변, 커밋/PR 본문,
|
|
6
|
+
// 프로젝트 문서)에 그대로 옮기면 독자는 그 참조를 해석할 수 없다.
|
|
7
|
+
//
|
|
8
|
+
// 이 모듈은 `okstra setup` 이 프로젝트 루트에 **이미 있는** `CLAUDE.md` /
|
|
9
|
+
// `AGENTS.md` 끝에 붙이는 관리 블록을 렌더하고 적용한다. 두 파일을 새로
|
|
10
|
+
// 만들지는 않는다 — 없는 프로젝트는 host 지침 문서를 쓰지 않는다는 뜻이다.
|
|
11
|
+
// 마커 규약은 `scripts/okstra_ctl/group_context.py` 의 task-memory 영역과 같다.
|
|
12
|
+
import { promises as fs } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
export const GUIDANCE_BEGIN = "<!-- okstra:citation-guidance:begin -->";
|
|
15
|
+
export const GUIDANCE_END = "<!-- okstra:citation-guidance:end -->";
|
|
16
|
+
/** 관리 대상 host 지침 문서. 존재하는 것만 갱신한다. */
|
|
17
|
+
export const GUIDANCE_TARGET_FILES = ["CLAUDE.md", "AGENTS.md"];
|
|
18
|
+
const GUIDANCE_BODY = [
|
|
19
|
+
"<!-- Managed by `okstra setup`. Edit above this block; this region is rewritten on each setup run. -->",
|
|
20
|
+
"",
|
|
21
|
+
"## Citing okstra artifacts",
|
|
22
|
+
"",
|
|
23
|
+
"okstra keeps its reports, decisions, and ledgers under `.okstra/`, written for readers of those reports.",
|
|
24
|
+
"Outside an okstra report — chat answers, commit messages, PR bodies, project docs, issue comments — do not",
|
|
25
|
+
"cite okstra-internal references (report section numbers, finding or clarification ids such as `C-001`,",
|
|
26
|
+
"run and stage ids, `.okstra/...` paths) as if the reader could resolve them. State the finding itself and",
|
|
27
|
+
"cite source code as `path:line`; name an okstra file only when the reader has been pointed to it.",
|
|
28
|
+
].join("\n");
|
|
29
|
+
/** 마커로 감싼 블록 전문. 앞뒤 개행은 붙이는 쪽에서 관리한다. */
|
|
30
|
+
export function renderGuidanceBlock() {
|
|
31
|
+
return `${GUIDANCE_BEGIN}\n${GUIDANCE_BODY}\n${GUIDANCE_END}`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 문서 본문에 블록을 반영한 결과를 돌려준다. 기존 블록이 있으면 그 자리에서
|
|
35
|
+
* 교체하고(문안이 바뀌어도 사용자가 쓴 앞부분은 그대로), 없으면 문서 끝에
|
|
36
|
+
* 붙인다. 내용이 이미 같으면 `changed: false` 로 파일을 건드리지 않는다.
|
|
37
|
+
*/
|
|
38
|
+
export function applyGuidance(content) {
|
|
39
|
+
const block = renderGuidanceBlock();
|
|
40
|
+
const begin = content.indexOf(GUIDANCE_BEGIN);
|
|
41
|
+
const end = content.indexOf(GUIDANCE_END);
|
|
42
|
+
if (begin !== -1 && end > begin) {
|
|
43
|
+
const next = content.slice(0, begin) + block + content.slice(end + GUIDANCE_END.length);
|
|
44
|
+
return { content: next, changed: next !== content };
|
|
45
|
+
}
|
|
46
|
+
const base = content.replace(/\s*$/, "");
|
|
47
|
+
const next = base.length === 0 ? `${block}\n` : `${base}\n\n${block}\n`;
|
|
48
|
+
return { content: next, changed: true };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 프로젝트 루트의 host 지침 문서를 갱신하고, 실제로 쓴 파일 경로를 돌려준다.
|
|
52
|
+
* 없는 파일은 건너뛴다. `AGENTS.md` 가 `CLAUDE.md` 심링크인 흔한 배치에서는
|
|
53
|
+
* 같은 실파일을 두 번 쓰지 않도록 realpath 로 중복을 제거한다.
|
|
54
|
+
*/
|
|
55
|
+
export async function ensureCitationGuidance(projectRoot) {
|
|
56
|
+
const written = [];
|
|
57
|
+
const seen = new Set();
|
|
58
|
+
for (const name of GUIDANCE_TARGET_FILES) {
|
|
59
|
+
const target = join(projectRoot, name);
|
|
60
|
+
let real;
|
|
61
|
+
try {
|
|
62
|
+
real = await fs.realpath(target);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
continue; // 파일이 없거나 끊어진 심링크 — okstra 가 새로 만들지는 않는다.
|
|
66
|
+
}
|
|
67
|
+
if (seen.has(real))
|
|
68
|
+
continue;
|
|
69
|
+
seen.add(real);
|
|
70
|
+
const content = await fs.readFile(target, "utf8");
|
|
71
|
+
const applied = applyGuidance(content);
|
|
72
|
+
if (!applied.changed)
|
|
73
|
+
continue;
|
|
74
|
+
await fs.writeFile(target, applied.content, "utf8");
|
|
75
|
+
written.push(target);
|
|
76
|
+
}
|
|
77
|
+
return written;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=citation-guidance.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"citation-guidance.mjs","sourceRoot":"","sources":["../../src/lib/citation-guidance.mts"],"names":[],"mappings":"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"}
|
|
@@ -220,11 +220,13 @@ The following former files are no longer generated by okstra.
|
|
|
220
220
|
~/.okstra/managers/<manager-id>/
|
|
221
221
|
├── manager.json
|
|
222
222
|
├── projects.json
|
|
223
|
+
├── view/index.html
|
|
223
224
|
└── task-groups/<safe-task-group>/<safe-task-id>/
|
|
224
225
|
├── manifest.json
|
|
225
226
|
├── children.json
|
|
226
227
|
├── directives.jsonl
|
|
227
228
|
├── snapshots.json
|
|
229
|
+
├── split-plan.json
|
|
228
230
|
├── events.jsonl
|
|
229
231
|
└── child-context/<safe-project-id>-<safe-child-task-id>.md
|
|
230
232
|
```
|
|
@@ -234,9 +236,11 @@ Storage authority:
|
|
|
234
236
|
- `manager.json`: manager identity and schema version.
|
|
235
237
|
- `projects.json`: the projectId / projectRoot / role / tags registered with the manager. `projectRoot` must be an existing directory, and setup-equivalent registration is performed only when project-local `.okstra/project.json` is absent.
|
|
236
238
|
- `manifest.json`, `children.json`, `directives.jsonl`: manager-owned plan, child assignment, and shared/project directives.
|
|
239
|
+
- `split-plan.json`: the last tracker split plan `task split` applied. The briefs it produced live in each child project's `.okstra/briefs/`, and the children it registered carry `ticketId`, `briefPath`, `scope` and `recommendedPhase` in `children.json`.
|
|
237
240
|
- `snapshots.json`: a read-side snapshot that `task sync` imports from the child project's `.okstra`. It is not the source of truth for project state.
|
|
238
241
|
- `events.jsonl`: manager events such as `task-created` and `child-launch-prepared`.
|
|
239
242
|
- `child-context/*.md`: child lead context prepared by `task run`. Sibling project reports/snapshots are passed only as read-side source material.
|
|
243
|
+
- `view/index.html`: the page `okstra manager view` writes from the files above. It is a derived view, rewritten on every run; it does not read child projects, so it shows each task as of its last `task sync`.
|
|
240
244
|
|
|
241
245
|
Path segments are normalized into slugs. If a slug would be empty, as can happen with non-ASCII values, a `u-<sha1-prefix>` fallback segment is used. However, `manifest.json` and the child `taskKey` preserve the original input values (`project-id:task-group:task-id`).
|
|
242
246
|
|
package/docs/architecture.md
CHANGED
|
@@ -128,7 +128,7 @@ Runtime entry points are consolidated in Python packages. Bash and skills only c
|
|
|
128
128
|
- [`okstra_ctl.session`](../scripts/okstra_ctl/session.py) · [`okstra_ctl.seeding`](../scripts/okstra_ctl/seeding.py) — Claude session ID / resume command / installation validation / runtime settings.
|
|
129
129
|
- [`okstra_ctl.{ids,index,invocation,jsonl,project_meta,reconcile,sequence,backfill,listing,locks}`](../scripts/okstra_ctl/) — existing central-index (`~/.okstra`) modules.
|
|
130
130
|
- [`okstra_project.{resolver,state}`](../scripts/okstra_project/) — PROJECT_ROOT resolution + project.json upsert + task-catalog/manifest reader.
|
|
131
|
-
- [`okstra_ctl.manager_cli`](../scripts/okstra_ctl/manager_cli.py), [`manager_store`](../scripts/okstra_ctl/manager_store.py), [`manager_sync`](../scripts/okstra_ctl/manager_sync.py), [`manager_launch`](../scripts/okstra_ctl/manager_launch.py), [`manager_paths`](../scripts/okstra_ctl/manager_paths.py) — cross-project manager state, one-way project snapshot sync,
|
|
131
|
+
- [`okstra_ctl.manager_cli`](../scripts/okstra_ctl/manager_cli.py), [`manager_store`](../scripts/okstra_ctl/manager_store.py), [`manager_sync`](../scripts/okstra_ctl/manager_sync.py), [`manager_launch`](../scripts/okstra_ctl/manager_launch.py), [`manager_paths`](../scripts/okstra_ctl/manager_paths.py), [`manager_view`](../scripts/okstra_ctl/manager_view.py), [`manager_split`](../scripts/okstra_ctl/manager_split.py) — cross-project manager state, one-way project snapshot sync, child launch packet/context creation, the HTML overview page, and tracker split into per-project scoped briefs for `okstra manager`.
|
|
132
132
|
|
|
133
133
|
### Bash entry points (thin)
|
|
134
134
|
|
package/docs/cli.md
CHANGED
|
@@ -823,7 +823,7 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
|
|
|
823
823
|
| `okstra doctor [--runtime claude-code\|codex\|antigravity\|external\|all] [--phase <phase>] [--json]` | Diagnose the runtime, Python imports, skill/agent installation, and model-pool state. The JSON `modelPool` object reports pool errors, default errors, binding precision, observed model, max write boundary, and invocation downgrade reason. It never sends an inference call. The `codex`, `antigravity`, and `external` runtimes omit Claude skill checks. `--phase` adds readiness checks for `implementation`, `final-verification`, `release-handoff`, or `improvement-discovery` |
|
|
824
824
|
| `okstra model list [--role <role>] [--host <host>] [--json]` | List catalog models for a host and optional role. Unselectable exact bindings report `exact binding unavailable`. No inference call |
|
|
825
825
|
| `okstra model default set\|unset <role> ... --scope project\|global [--cwd <dir>]` | Atomically write or remove `modelDefaults` for one canonical role |
|
|
826
|
-
| `okstra setup --project-id <id>` | Create or update `.okstra/project.json` in the current project |
|
|
826
|
+
| `okstra setup --project-id <id>` | Create or update `.okstra/project.json` in the current project. When `<PROJECT_ROOT>/CLAUDE.md` or `AGENTS.md` already exists, it also appends — or refreshes in place — an okstra-managed block between `<!-- okstra:citation-guidance:begin -->` / `end` telling agents not to cite okstra-internal references (report section numbers, `C-NNN` ids, run/stage ids, `.okstra/...` paths) outside an okstra report. Neither file is created, and a failure is a warning, not an error |
|
|
827
827
|
| `okstra check-project [--json]` | Verify that the current project is registered |
|
|
828
828
|
| `okstra preflight [--runtime <name>] [--cwd <dir>] [--machine]` | Single skill-preflight call combining `ensure-installed`, with silent reinstall when stale, `check-project`, and host-specific `runtimeReadiness`. It defaults to a fixed text projection. `--machine` returns the automation JSON contract, and `--json` is a deprecated one-release alias. A `claude-code` host checks project workspace trust. A `codex` current-session host verifies write access to `~/.okstra/worktrees/registry.lock`; a sandbox denial blocks before the wizard with the `switch-codex-to-full-access-and-rerun` action. `antigravity` and `external` hosts return ready without reading Claude Code state. Step 0 of every project-scoped skill converges on this command |
|
|
829
829
|
| `okstra convergence seed --groups <path> --work-state <path> --final-state <path> --migration-dir <dir> [--restart-from-round0]` | Create, resume, reuse, or explicitly recover deterministic convergence state |
|
|
@@ -856,7 +856,7 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
|
|
|
856
856
|
| `okstra model-io active-context-input --project-root <dir> --run-manifest <path>` | Resolve the prior implementation-planning active context only through the current project and run authority, then emit fixed fields such as the executor base ref. Caller-supplied active-context paths are not accepted. |
|
|
857
857
|
| `okstra report-translate <source\|write\|check-data> --run-manifest <path> …` | Resolve the report data and translation sidecar from the run manifest. `source` emits a source digest with the fixed translation queue; `write` requires that digest and rejects a stale report; `check-data` verifies the derived sidecar. Models never choose the data or sidecar path. |
|
|
858
858
|
| `okstra convergence prepare-groups --run-manifest <path> --input <grouping.md>` | Parse fixed grouping Markdown, validate distinct per-worker evidence and group semantics against the run authority, then publish the schema-valid groups artifact at the run-owned path. |
|
|
859
|
-
| `okstra manager <init\|discover-projects\|new\|task> [--json]` | Public CLI for grouping cross-project okstra tasks into manager-owned context. The default is purpose-specific fixed text for model use; `--json` preserves the machine contract and exit codes. `new project --project-root` accepts only existing directories and performs setup-equivalent registration only if `.okstra/project.json` is absent. |
|
|
859
|
+
| `okstra manager <init\|discover-projects\|new\|task\|list\|view> [--json]` | Public CLI for grouping cross-project okstra tasks into manager-owned context. The default is purpose-specific fixed text for model use; `--json` preserves the machine contract and exit codes. `new project --project-root` accepts only existing directories and performs setup-equivalent registration only if `.okstra/project.json` is absent. `list managers`, `list projects --manager-id <id>` and `list tasks --manager-id <id>` enumerate manager state; `view --manager-id <id>` writes `~/.okstra/managers/<id>/view/index.html` and prints its path and `file://` URL. `task split --plan <json>` writes one validated brief with a `## Project Scope` section into each project an issue is assigned to and registers those children; `task run` then passes `--task-brief`. |
|
|
860
860
|
| `okstra rollup [--task-group <group>] [--project-root <dir>] [--cwd <dir>] [--text\|--json]` | Read-only backend for the okstra-rollup skill. `--text` emits ordered fixed labels for model use. The default and `--json` preserve the full machine JSON contract and exit codes. Omitting `--task-group` targets the whole project catalog. |
|
|
861
861
|
| `okstra usage-report [--days <positive-int>] [--project-root <dir>] [--cwd <dir>] [--text\|--json]` | Read-only backend for the okstra-usage skill. `--text` emits ordered fixed labels for model use. The default and `--json` preserve the full machine JSON contract and exit codes. Defaults to the current project's last 30 days. |
|
|
862
862
|
| `okstra worker-state transition --team-state <path> --worker <id> --status <in-progress\|completed\|timeout\|error\|not-run> [--reason <text>] [--model <execution-value>]` | Atomically update one persisted worker row. `in-progress` records the authoritative `startedAt` and clears `endedAt`; terminal states record `endedAt`; `timeout`, `error`, and `not-run` require a reason. Dispatch adapters use this same transition path, so CLI-backed and in-process orchestration share the status timestamp contract |
|
|
@@ -33,8 +33,19 @@ okstra manager task note --manager-id <manager-id> --task-group <task-group> --t
|
|
|
33
33
|
okstra manager task sync --manager-id <manager-id> --task-group <task-group> --task-id <task-id>
|
|
34
34
|
okstra manager task status --manager-id <manager-id> --task-group <task-group> --task-id <task-id>
|
|
35
35
|
okstra manager task run --manager-id <manager-id> --project-id <project-id> --task-group <task-group> --task-id <task-id> [--child-task-id <child-task-id>]
|
|
36
|
+
okstra manager task split --manager-id <manager-id> --task-group <task-group> --task-id <task-id> --plan <split-plan.json> [--overwrite]
|
|
37
|
+
okstra manager list managers
|
|
38
|
+
okstra manager list projects --manager-id <manager-id>
|
|
39
|
+
okstra manager list tasks --manager-id <manager-id>
|
|
40
|
+
okstra manager view --manager-id <manager-id>
|
|
36
41
|
```
|
|
37
42
|
|
|
43
|
+
- `list managers`: every manager in this home with its project count and manager-task count.
|
|
44
|
+
- `list projects`: the manager's registered projects with root, role, tags and link time.
|
|
45
|
+
- `list tasks`: the manager's tasks by task-group and task-id with objective, progress mode and child count.
|
|
46
|
+
- `task split`: reads a tracker split plan (one Linear project or parent issue, each issue assigned to one or more projects with a scope), writes one brief per (issue, project) pair to `<projectRoot>/.okstra/briefs/<task-group>/<ticketId>-<file-title>.md` with a `## Project Scope` section, and registers each pair as child `<project-id>:<task-group>:<slug(ticketId)>`. Every brief passes the brief validator before any file is written; an existing brief with different content stops the run unless `--overwrite` is given. The skill's Tracker Split section defines the plan JSON and the fetch/confirm procedure.
|
|
47
|
+
- `view`: writes `view/index.html` (summary tiles, project table, one panel per task with child rows, directives and events) and returns `View path` and `View URL`. It does not sync; each panel shows its last sync time and the sync command.
|
|
48
|
+
|
|
38
49
|
## Storage Model
|
|
39
50
|
|
|
40
51
|
Manager state is stored under `~/.okstra/managers/<manager-id>/`, split into two layers: the manager root and the task directory.
|
|
@@ -47,11 +58,13 @@ Directly under the manager root:
|
|
|
47
58
|
Under the task directory `task-groups/<safe-group>/<safe-task>/`:
|
|
48
59
|
|
|
49
60
|
- `manifest.json`: manager task objective, common brief, progress mode
|
|
50
|
-
- `children.json`: child task plan, assignment, launch metadata
|
|
61
|
+
- `children.json`: child task plan, assignment, launch metadata; children made by `task split` also carry `ticketId`, `briefPath`, `scope` and `recommendedPhase`
|
|
62
|
+
- `split-plan.json`: the last plan `task split` applied
|
|
51
63
|
- `directives.jsonl`: shared/project directive rows
|
|
52
64
|
- `snapshots.json`: the read-side snapshot `task sync` read from the project-local `.okstra`
|
|
53
|
-
- `events.jsonl`: manager events such as `task-created` and `child-launch-prepared`
|
|
65
|
+
- `events.jsonl`: manager events such as `task-created`, `task-split` and `child-launch-prepared`
|
|
54
66
|
- `child-context/<safe-project>-<safe-task>.md`: the child lead context `task run` produced
|
|
67
|
+
- `view/index.html` (under the manager root): the overview page `view` writes; rewritten on every run
|
|
55
68
|
|
|
56
69
|
A segment whose slug is empty (e.g. a non-ASCII task-group/task-id) uses a `u-<sha1-prefix>` path segment, but the manifest and the child `taskKey` preserve the original input value.
|
|
57
70
|
|
|
@@ -60,10 +73,14 @@ A segment whose slug is empty (e.g. a non-ASCII task-group/task-id) uses a `u-<s
|
|
|
60
73
|
`task run` does not run the child work directly; it prepares a launch packet. The key fixed fields of the returned packet:
|
|
61
74
|
|
|
62
75
|
- `Task key`: the child's `project-id:task-group:task-id` (the public child-identity key — also recorded on the `child-launch-prepared` event)
|
|
63
|
-
- `Backend`: always `
|
|
76
|
+
- `Backend`: always `spawn-process` — the child lead runs as a separate host process started by the installed launcher
|
|
64
77
|
- `Worker dispatch backend`: always `subagent` in v1
|
|
65
78
|
- `Project root`: the child project root
|
|
66
79
|
- `Context path`: the manager child context markdown
|
|
67
|
-
-
|
|
80
|
+
- `Run command`: the installed launcher, `~/.okstra/bin/okstra.sh`
|
|
81
|
+
- every numbered `Run arg N`: the ordered launcher arguments `--project-root … --project-id … --task-group … --task-id … --directive "Read manager child context: …"`. A child made by `task split` also gets `--task-brief <briefPath>`, and `--task-type <recommendedPhase>` while the child task does not exist yet in its project
|
|
82
|
+
- `Shell command`: `Run command` plus every `Run arg N`, shell-quoted; the skill hands this line to the user to run in a new terminal
|
|
83
|
+
|
|
84
|
+
The launcher fills the task type and brief from the child task manifest when it exists and asks for the rest, then prepares the run and starts the lead with the directive in its prompt. `okstra run` is not a substitute: for a lead host it adds `--launch-only`, which drops the task inputs and the directive.
|
|
68
85
|
|
|
69
86
|
When packet creation succeeds, that child launch's status in `children.json` is updated to `prepared`, and a `child-launch-prepared` is appended to `events.jsonl`. On failure it does not modify the project-local task state.
|
|
@@ -91,6 +91,15 @@ Create command:
|
|
|
91
91
|
okstra setup --yes --project-root /abs/project --project-id my-project
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
+
`okstra setup` also refreshes the okstra-managed citation-guidance block in
|
|
95
|
+
`<PROJECT_ROOT>/CLAUDE.md` and `AGENTS.md` when those files already exist (it never
|
|
96
|
+
creates them). The block tells agents not to carry okstra-internal references — report
|
|
97
|
+
section numbers, `C-NNN` clarification ids, run/stage ids, `.okstra/...` paths — into
|
|
98
|
+
writing that is not an okstra report, where the reader cannot resolve them. The command
|
|
99
|
+
reports the files it touched in its JSON `citationGuidance` array; a failure there is a
|
|
100
|
+
`warning:` line, not a non-zero exit. Tell the user which guidance files were updated so
|
|
101
|
+
they can review the appended block.
|
|
102
|
+
|
|
94
103
|
## Optional configuration
|
|
95
104
|
|
|
96
105
|
Per the source, Step 3.5 is optional configuration performed only when the user explicitly wants it — read `references/project-config.md` in the skill directory for the detailed procedure. If the defaults are enough, skip it and go to doctor.
|
|
@@ -327,10 +327,13 @@ Important modules:
|
|
|
327
327
|
| `plan_run_root.py` | shared helper deriving `approved_plan_path` → `plan_run_root` and back-tracing the task-key |
|
|
328
328
|
| `manager_cli.py` | `okstra manager` Python entrypoint — purpose-specific fixed text by default, machine JSON with `--json` |
|
|
329
329
|
| `manager_paths.py` | Manager state path SSOT under `~/.okstra/managers/<manager-id>/`; slug fallback uses `u-<sha1-prefix>` when a safe segment would be empty |
|
|
330
|
-
| `manager_store.py` | Manager-owned state mutation — project membership, task planning, assignment, directives, event append |
|
|
330
|
+
| `manager_store.py` | Manager-owned state mutation — project membership, task planning, assignment, directives, event append — plus the `list managers/projects/tasks` readers |
|
|
331
331
|
| `manager_sync.py` | One-way child project `.okstra` snapshot reader; corrupt child state becomes row-level `error` so other children continue |
|
|
332
332
|
| `manager_launch.py` | Child launch packet and manager child context renderer; records `prepared` launch metadata/events without changing project-local task state |
|
|
333
|
+
| `manager_view.py` | `okstra manager view` — renders one manager's projects, tasks, child summaries, directives and events into `view/index.html` from `templates/manager/view.template.html`; reads manager-owned files only |
|
|
334
|
+
| `manager_split.py` | `okstra manager task split` — validates a tracker split plan, renders one brief per (issue, project) with a `## Project Scope` section, checks each with `validators/validate-brief.py` before writing, and registers the children |
|
|
333
335
|
| `agent/invocation.py` | Deep invocation-contract module — composes model assignment, common/functional duty, and task instructions; publishes immutable prompt/metadata pairs; verifies five digests; owns standalone result/completion envelopes |
|
|
336
|
+
| `agent/evidence_recovery.py` | Issues a same-model, same-role evidence-recovery invocation over a terminal-state attempt — verifies the source invocation metadata and existing result, then republishes a suffixed prompt/metadata/result triple (`-evidence-recovery-<attempt>`) that preserves the original model assignment while confining the child to preserving the earlier result's evidence rather than re-running the completed work (`prepare_evidence_recovery`, driven from `dispatch_core`) |
|
|
334
337
|
| `agent/prompt_cli/` | CLI boundary for run-backed and standalone materialization/verification plus host-native dispatch and result-link records. `inputs` resolves the path arguments this model-facing surface cannot trust and `emit` writes the result; above them `run_identity` refuses a role the run never issued and `dynamic_verifier` reserves a re-verification slot only after that role qualifies; `materialize` authors the specification (every report-writer prompt gets the okstra-rendered `## Output`; with `--corrections` it runs the ledger check in `corrections` and prepends `## Corrections`; without one it refuses a corrective dispatch over a parsing narrative), `results` links what came back, and `cli` is the argparse surface with the command's canonical USAGE epilog (`check-corrections` runs the ledger check without materializing; `apply-corrections` writes a mechanical ledger to the narrative and records the `lead-correction-applied` activity row via `corrections.run_corrections_apply`) |
|
|
335
338
|
| `dispatch_state.py` | Provider-neutral `WorkerJob`, invocation metadata validation, immutable host-native dispatch/result-link recording, and shared team-state mutation helpers |
|
|
336
339
|
| `dispatch_core.py` | Backend-neutral worker dispatch core — verifies invocation metadata immediately before worker execution, then records and collects code-owned process/pane attempts shared by every lead runtime |
|
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -76,7 +76,7 @@ For every other task type:
|
|
|
76
76
|
- Pointer `status: pending` with a `rationale` → the user's own input is what comes next. Quote the `rationale` and issue the command it names. Do not re-run the phase that just completed, and do not send the user to `/okstra-inspect`: a finished phase has nothing to inspect, and re-running it discards the result the user is being asked to act on.
|
|
77
77
|
- Otherwise → `/okstra-inspect status` for this task.
|
|
78
78
|
|
|
79
|
-
For file links in progress updates and closeout, use a short descriptive label in the Report Language, such as `[<선택 언어> 보고서 열기](<
|
|
79
|
+
For file links in progress updates and closeout, use a short descriptive label in the Report Language, such as `[<선택 언어> 보고서 열기](<absolute-report-path>)`, replacing `<선택 언어>` with the run's selected report language name. The destination is always an absolute path: a project-relative destination does not open when the host's working directory differs or the user opens the file in an external program. `reportPaths.markdown` destinations are already absolute; for a project-relative path from this prompt, prefix `{{PROJECT_ROOT}}/`. Wrap a destination that contains spaces in `<...>`. Put each link on its own line. Keep the complete destination on one source line without inserted newlines; never abbreviate the destination or repeat the long path beside the label. Change only the display label. Commands stay in backticks.
|
|
80
80
|
|
|
81
81
|
Some terminal hosts expand Markdown links into visible paths. If that happens, provide a copyable file-opening command for the verified host OS in a code block, on one source line with the actual path safely shell-quoted (for example, `open` on macOS). Do not execute it unless asked. Do not promise that Markdown prevents visual wrapping, or create a shortened copy or symlink merely for display. This is presentation guidance, not runtime validation of the emitted message.
|
|
82
82
|
|
|
@@ -106,7 +106,7 @@ User-utterance interpretation rule:
|
|
|
106
106
|
|
|
107
107
|
At each phase or implementation-stage boundary, at task completion, and before a controlled session pause or handoff, follow the launch prompt's "Progress, remaining work, and recommendation" guidance. Include the result and evidence, unfinished work and blockers, and the next action with its reason. During an authorized continuous run, deliver this update beside the checkpoint and continue; do not turn the update into an approval gate. The same guidance applies to the final reply after persistence below.
|
|
108
108
|
|
|
109
|
-
Apply the launch prompt's file-link presentation guidance to these updates as well: short localized labels, one link per line, an intact
|
|
109
|
+
Apply the launch prompt's file-link presentation guidance to these updates as well: short localized labels, one link per line, an intact absolute destination, and a copyable file-opening command when the terminal expands links. Keep file links separate from the raw progress checkpoint line.
|
|
110
110
|
|
|
111
111
|
Follow the launch prompt's operation-level progress guidance: name the actual prepared artifact or executed check, distinguish preparation from execution and results, and cite the observed error when blocked. Apply its existing-authorization guidance before asking about provider data transfer again; reuse approval for the same recipients and material while respecting separate host execution permission.
|
|
112
112
|
|
|
@@ -529,7 +529,7 @@ When the host native picker is available and two of those rows could apply, ask
|
|
|
529
529
|
|
|
530
530
|
**Cite run-artifact paths, do not assemble them.** The `report-finalize` result's `reportPaths` carries this run's `humanReport`, `reportRecord`, `teamState`, and `renderFullCopy` command, each already rooted at the project (`.okstra/tasks/<task-group>/<task-id>/runs/...`). Every other run-artifact path the reply cites — the resume command among them — comes from the launch prompt's `## Manifests` / `## Run Paths` lists, which are rooted the same way. A path you compose from a `runs/<task-type>/...` pattern instead is identical across every task of that task-type, so it names no task and does not resolve from the project root either.
|
|
531
531
|
|
|
532
|
-
Write file references as Markdown links with short labels in the Report Language, following the launch prompt's file-link presentation guidance. `reportPaths.markdown` supplies
|
|
532
|
+
Write file references as Markdown links with short labels in the Report Language, following the launch prompt's file-link presentation guidance. `reportPaths.markdown` supplies absolute destinations for the report, report record, and team state; preserve those destinations and shorten only their display labels. Put each link on its own line without repeating the path in prose or inserting a line break inside the destination. Apply the same format to worker results, error logs, and briefs: take the project-rooted path from `## Manifests` / `## Run Paths` and prefix the absolute project root, so every link destination is absolute. Relative paths stay in commands, not in link destinations. If the terminal expands links into long paths, offer the host-appropriate copyable file-opening command described in the launch prompt. Markdown alone does not guarantee clickable links in every terminal.
|
|
533
533
|
|
|
534
534
|
**Enforced:** `scripts/okstra_ctl/report_finalize.py` `_closeout_report_paths` builds the task-qualified paths and `closeout_command` builds the close, so neither is re-derived; `validators/validate_session_conformance.py` `_check_progress_checkpoints` requires the `phase-7-persist` checkpoint this phase opens with.
|
|
535
535
|
|
|
@@ -23,7 +23,7 @@ Every verifier acts as a QA gate, not just a diff reviewer. Trusting the executo
|
|
|
23
23
|
|
|
24
24
|
### Record the verification target
|
|
25
25
|
|
|
26
|
-
Before each declared check, run `okstra verification-target --project-root <project-root> --run-manifest <run-manifest> --expected-head <executor-head> --command <exact-declared-command>` and preserve its JSON output under this run's artifact directory. This tool reads the active run's recorded worktree and checks its HEAD and source fingerprint; it never executes the command. Execute the
|
|
26
|
+
Before each declared check, run `okstra verification-target --project-root <project-root> --run-manifest <run-manifest> --expected-head <executor-head> --command <exact-declared-command>` and preserve its JSON output under this run's artifact directory. This tool reads the active run's recorded worktree and checks its HEAD and source fingerprint; it never executes the command. Execute exactly the returned `target.command` through the host's authorized tool with `target.cwd` as its separate working-directory argument. The tool separates a legacy trailing `; existing helper: <file>:<lines>` annotation and removes a leading `cd` only when it names that exact worktree. Preserve `declaredCommand` and `commandAnnotation` when returned; never independently trim prose or rewrite shell syntax. Repeat the target check with the same original declaration and `--baseline <saved-json>` after execution. Include both target-check results and the actual command outcome in `readOnlyCommandLog`. Enforcement: `capture_verification_target` and `test_verification_target_returns_one_command_without_executing_it`.
|
|
27
27
|
|
|
28
28
|
A target mismatch or changed fingerprint invalidates this check's evidence. Record it as an execution-target problem, preserve the source, and rerun only the affected verification after the lead resolves it. An unavailable environment or a check that never ran is not a code rejection. A matching target fingerprint proves source stability at the two observations; it does not prove the command ran or that the test covers the requirement. Keep the independent command outcome and coverage assessment.
|
|
29
29
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Implementation Planning Profile
|
|
2
2
|
|
|
3
|
+
Keep `commandOrObservation` executable when the checklist calls for a command. Put explanations and existing-helper citations in the checklist's `check` text, outside the command. Do not append prose after a semicolon. The plan-item renderer separates legacy `existing helper: <file>:<lines>` suffixes, and `verification-target` supplies the same executable command to every verifier.
|
|
4
|
+
|
|
3
5
|
Validation commands are executable inputs: preserve their newlines, quotes, and code bodies. Use frozen dependency installation (`npm ci`, `pnpm install --frozen-lockfile`, or the package manager's equivalent). Name QA scripts with absolute task-artifact paths when a command runs from a stage worktree; `.okstra/tasks/...` relative to that worktree does not point to the project task. `stage_validation_executability_errors` enforces command restrictions at planning validation, implementation entry, and report preflight, including advisory plan-body runs.
|
|
4
6
|
|
|
5
7
|
Plan for the actual worktree layout before approval. Shared documentation directories can be links to the main checkout. Choose a build command compatible with those links (for example, an installed Next.js version may provide `next build --webpack`); verify the available option rather than assuming it. Do not plan for a verifier to move links or repair its environment. Compare negative-case assertions with the brief and the script body: a requirement to cache existing assets does not establish that missing assets should be cached. Record any changed expectation in a new plan revision; preserve the earlier approved plan.
|
|
@@ -28,7 +28,7 @@ from dataclasses import dataclass
|
|
|
28
28
|
from pathlib import Path
|
|
29
29
|
from typing import Any, Mapping, Sequence
|
|
30
30
|
|
|
31
|
-
from .convergence_provenance import worker_result_suffix
|
|
31
|
+
from .convergence_provenance import worker_result_paths, worker_result_suffix
|
|
32
32
|
from .fixed_text import line
|
|
33
33
|
from .json_boundary import JsonBoundaryError, load_owned_object
|
|
34
34
|
from .paths import RunRef
|
|
@@ -132,9 +132,7 @@ def analyser_results(
|
|
|
132
132
|
) -> list[AnalyserResult]:
|
|
133
133
|
"""분석 audience 워커마다 결과 파일 경로와 finding id 를 짝지어 돌려준다.
|
|
134
134
|
|
|
135
|
-
|
|
136
|
-
`<worker>-worker-<task-type>-<workerResults seq>.md`. state 시퀀스와 다른
|
|
137
|
-
카운터라 그룹 파일명에서 접미사를 빌려 오면 빗나간다.
|
|
135
|
+
출처 검사와 같은 실행 원장을 사용해야 교정 전 결과를 재검증하지 않는다.
|
|
138
136
|
"""
|
|
139
137
|
suffix = worker_result_suffix(Path(run_dir), groups)
|
|
140
138
|
if suffix is None:
|
|
@@ -142,13 +140,13 @@ def analyser_results(
|
|
|
142
140
|
"cannot resolve the worker-result suffix from the grouping's "
|
|
143
141
|
"runManifestPath"
|
|
144
142
|
)
|
|
145
|
-
|
|
143
|
+
paths = worker_result_paths(Path(run_dir), groups, suffix)
|
|
146
144
|
by_worker = _findings_by_worker(groups)
|
|
147
145
|
return [
|
|
148
146
|
AnalyserResult(
|
|
149
147
|
worker_id=worker,
|
|
150
148
|
result_path=_project_relative(
|
|
151
|
-
project_root,
|
|
149
|
+
project_root, paths[worker]
|
|
152
150
|
),
|
|
153
151
|
finding_ids=tuple(by_worker.get(worker, ())),
|
|
154
152
|
)
|
|
@@ -21,6 +21,8 @@ from pathlib import Path
|
|
|
21
21
|
from typing import Mapping
|
|
22
22
|
|
|
23
23
|
from okstra_ctl.convergence_engine import grouped_input_digest
|
|
24
|
+
from okstra_ctl.execution_identity import Attempt, ExecutionManifest
|
|
25
|
+
from okstra_ctl.execution_manifest import read_execution_manifest_view
|
|
24
26
|
from okstra_ctl.json_boundary import (
|
|
25
27
|
load_owned_object,
|
|
26
28
|
)
|
|
@@ -97,6 +99,66 @@ def groups_digest_ok(state_dir: Path, suffix: str, document: dict) -> bool:
|
|
|
97
99
|
return grouped_input_digest(document) == recorded
|
|
98
100
|
|
|
99
101
|
|
|
102
|
+
def discovery_attempts(manifest: ExecutionManifest) -> dict[str, list[Attempt]]:
|
|
103
|
+
"""교정 실행은 발견 결과를 대체하지만 재검증 투표는 발견 출처가 아니다."""
|
|
104
|
+
latest: dict[str, Attempt] = {}
|
|
105
|
+
for attempt in manifest.attempts:
|
|
106
|
+
current = latest.get(attempt.invocation_ref)
|
|
107
|
+
if current is None or attempt.attempt > current.attempt:
|
|
108
|
+
latest[attempt.invocation_ref] = attempt
|
|
109
|
+
by_role: dict[str, list[Attempt]] = {}
|
|
110
|
+
for invocation in manifest.invocations:
|
|
111
|
+
if invocation.dispatch_kind not in {"initial", "correction"}:
|
|
112
|
+
continue
|
|
113
|
+
attempt = latest.get(invocation.invocation_ref)
|
|
114
|
+
if attempt is not None:
|
|
115
|
+
by_role.setdefault(invocation.role_execution_ref, []).append(attempt)
|
|
116
|
+
return by_role
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def worker_result_paths(
|
|
120
|
+
run_dir: Path, document: Mapping, suffix: str
|
|
121
|
+
) -> dict[str, Path]:
|
|
122
|
+
"""실행 원장의 채택된 발견 결과를 고른다. 구형 원장은 파일명 규칙을 쓴다."""
|
|
123
|
+
results_dir = run_dir / "worker-results"
|
|
124
|
+
workers = document.get("workers") or []
|
|
125
|
+
paths = {
|
|
126
|
+
row["workerId"]: results_dir / f"{row['workerId']}-worker-{suffix}.md"
|
|
127
|
+
for row in workers if isinstance(row, Mapping) and row.get("workerId")
|
|
128
|
+
}
|
|
129
|
+
if document.get("schemaVersion") != "2.0":
|
|
130
|
+
return paths
|
|
131
|
+
recorded = document.get("runManifestPath")
|
|
132
|
+
if not _nonempty_string(recorded):
|
|
133
|
+
return paths
|
|
134
|
+
manifest_path = run_dir / "manifests" / Path(recorded).name
|
|
135
|
+
manifest, authority = read_execution_manifest_view(manifest_path)
|
|
136
|
+
if manifest.legacy:
|
|
137
|
+
return paths
|
|
138
|
+
attempts = discovery_attempts(manifest)
|
|
139
|
+
for row in workers:
|
|
140
|
+
if not isinstance(row, Mapping) or not row.get("workerId"):
|
|
141
|
+
continue
|
|
142
|
+
accepted = [
|
|
143
|
+
attempt for attempt in attempts.get(row.get("sourceRoleExecutionRef"), [])
|
|
144
|
+
if attempt.status == "ok" and attempt.result_path
|
|
145
|
+
]
|
|
146
|
+
if not accepted:
|
|
147
|
+
continue
|
|
148
|
+
candidate = Path(accepted[-1].result_path)
|
|
149
|
+
if not candidate.is_absolute():
|
|
150
|
+
project_root = authority.get("projectRoot")
|
|
151
|
+
if not _nonempty_string(project_root):
|
|
152
|
+
raise ValueError("relative accepted resultPath requires projectRoot")
|
|
153
|
+
candidate = Path(project_root) / candidate
|
|
154
|
+
if candidate.resolve().parent != results_dir.resolve():
|
|
155
|
+
raise ValueError(f"accepted resultPath is outside worker-results: {candidate}")
|
|
156
|
+
if not candidate.is_file():
|
|
157
|
+
raise ValueError(f"accepted resultPath is missing: {candidate}")
|
|
158
|
+
paths[row["workerId"]] = candidate
|
|
159
|
+
return paths
|
|
160
|
+
|
|
161
|
+
|
|
100
162
|
def read_canonical_worker_result(
|
|
101
163
|
worker_results_dir: Path, worker: str, suffix: str
|
|
102
164
|
) -> str | None:
|
|
@@ -140,20 +202,25 @@ def provenance_errors(
|
|
|
140
202
|
claims = group_claims(document)
|
|
141
203
|
if claims is None:
|
|
142
204
|
return []
|
|
205
|
+
paths = worker_result_paths(worker_results_dir.parent, document, suffix)
|
|
143
206
|
contents: dict[str, str | None] = {}
|
|
144
207
|
errors: list[str] = []
|
|
145
208
|
for finding_id, worker, item_id in claims:
|
|
146
209
|
if worker not in contents:
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
210
|
+
candidate = paths.get(worker)
|
|
211
|
+
if candidate is not None and candidate.name != f"{worker}-worker-{suffix}.md":
|
|
212
|
+
contents[worker] = candidate.read_text(encoding="utf-8")
|
|
213
|
+
else:
|
|
214
|
+
contents[worker] = read_canonical_worker_result(
|
|
215
|
+
worker_results_dir, worker, suffix
|
|
216
|
+
)
|
|
150
217
|
text = contents[worker]
|
|
151
218
|
if text is None or id_occurs_wordbounded(text, item_id):
|
|
152
219
|
continue
|
|
153
220
|
errors.append(
|
|
154
221
|
f"convergence groups `{groups_label}` group {finding_id} "
|
|
155
222
|
f"cites source item `{worker}:{item_id}`, but that ID does not "
|
|
156
|
-
f"occur in the worker's result file `{worker}-worker-{suffix}.md` — a "
|
|
223
|
+
f"occur in the worker's result file `{paths.get(worker, Path(f'{worker}-worker-{suffix}.md')).name}` — a "
|
|
157
224
|
"grouping may not invent a provenance link to an item the "
|
|
158
225
|
"worker never reported."
|
|
159
226
|
)
|
|
@@ -243,10 +310,10 @@ DISCARDED_ATTEMPT_STATUSES = frozenset(
|
|
|
243
310
|
|
|
244
311
|
|
|
245
312
|
def discarded_workers(document: object, execution_manifest: object) -> dict[str, str]:
|
|
246
|
-
"""``{workerId: "<invocationRef>#<attempt> <status>"}`` —
|
|
313
|
+
"""``{workerId: "<invocationRef>#<attempt> <status>"}`` — 발견 attempt 가 전부
|
|
247
314
|
폐기돼 채택할 결과가 없는 분석 워커.
|
|
248
315
|
|
|
249
|
-
한 워커의
|
|
316
|
+
한 워커의 발견 invocation 이 둘 이상이면(교정 디스패치는 새 invocation 이다)
|
|
250
317
|
그중 하나라도 마지막 attempt 가 ``ok`` 면 채택 가능한 결과가 있는 것이므로
|
|
251
318
|
폐기로 보지 않는다. legacy 매니페스트나 v1 roster 는 판정하지 않는다.
|
|
252
319
|
"""
|
|
@@ -254,17 +321,7 @@ def discarded_workers(document: object, execution_manifest: object) -> dict[str,
|
|
|
254
321
|
return {}
|
|
255
322
|
if execution_manifest is None or getattr(execution_manifest, "legacy", False):
|
|
256
323
|
return {}
|
|
257
|
-
|
|
258
|
-
for invocation in getattr(execution_manifest, "invocations", ()):
|
|
259
|
-
if invocation.dispatch_kind == "initial":
|
|
260
|
-
initial_refs.setdefault(invocation.role_execution_ref, set()).add(
|
|
261
|
-
invocation.invocation_ref
|
|
262
|
-
)
|
|
263
|
-
latest: dict[str, object] = {}
|
|
264
|
-
for attempt in getattr(execution_manifest, "attempts", ()):
|
|
265
|
-
current = latest.get(attempt.invocation_ref)
|
|
266
|
-
if current is None or attempt.attempt > current.attempt:
|
|
267
|
-
latest[attempt.invocation_ref] = attempt
|
|
324
|
+
attempts = discovery_attempts(execution_manifest)
|
|
268
325
|
result: dict[str, str] = {}
|
|
269
326
|
for row in document["workers"]:
|
|
270
327
|
if not isinstance(row, dict):
|
|
@@ -273,7 +330,7 @@ def discarded_workers(document: object, execution_manifest: object) -> dict[str,
|
|
|
273
330
|
role_ref = row.get("sourceRoleExecutionRef")
|
|
274
331
|
if not _nonempty_string(worker) or not _nonempty_string(role_ref):
|
|
275
332
|
continue
|
|
276
|
-
rows =
|
|
333
|
+
rows = attempts.get(role_ref, [])
|
|
277
334
|
if not rows or any(item.status == "ok" for item in rows):
|
|
278
335
|
continue
|
|
279
336
|
discarded = [item for item in rows if item.status in DISCARDED_ATTEMPT_STATUSES]
|