okstra 0.201.2 → 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/bin/okstra +1 -1
- 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 +11 -5
- 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 +15 -3
- package/runtime/prompts/lead/team-contract.md +2 -0
- package/runtime/prompts/profiles/_implementation-executor.md +2 -2
- package/runtime/prompts/profiles/_implementation-verifier.md +7 -1
- package/runtime/prompts/profiles/implementation-planning.md +2 -0
- package/runtime/python/okstra_ctl/agent/evidence_recovery.py +78 -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/dispatch_core.py +91 -17
- package/runtime/python/okstra_ctl/execution_identity.py +9 -2
- package/runtime/python/okstra_ctl/execution_manifest.py +6 -2
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +134 -65
- 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 +3 -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
- package/runtime/validators/validate-run.py +7 -15
|
@@ -13,13 +13,14 @@ import hashlib
|
|
|
13
13
|
import argparse
|
|
14
14
|
import json
|
|
15
15
|
import re
|
|
16
|
+
import shlex
|
|
16
17
|
import subprocess
|
|
17
18
|
from pathlib import Path
|
|
18
19
|
|
|
19
20
|
from .execution_mutation_audit import source_content_snapshot
|
|
20
21
|
from .json_boundary import load_owned_object
|
|
21
22
|
from .path_hints import hydrate_active_run_context
|
|
22
|
-
from .qa_commands import verification_command_defects
|
|
23
|
+
from .qa_commands import split_verification_command, verification_command_defects
|
|
23
24
|
|
|
24
25
|
TARGET_FIELD_RES = {
|
|
25
26
|
"scope": re.compile(r"\*\*Verification scope:\*\*\s*`([^`]*)`"),
|
|
@@ -95,6 +96,13 @@ def capture_verification_target(
|
|
|
95
96
|
if not target or not Path(target).is_absolute():
|
|
96
97
|
raise ValueError("active run context has no absolute verification worktree")
|
|
97
98
|
worktree = Path(target).resolve()
|
|
99
|
+
declared_command = command
|
|
100
|
+
command, annotation = split_verification_command(command)
|
|
101
|
+
prefix = re.match(r"^\s*cd\s+('[^']*'|\"[^\"]*\"|[^\s;&|]+)\s*&&\s*", command)
|
|
102
|
+
if prefix is not None:
|
|
103
|
+
directory = shlex.split(prefix.group(1))[0]
|
|
104
|
+
if Path(directory).is_absolute() and Path(directory).resolve() == worktree:
|
|
105
|
+
command = command[prefix.end():]
|
|
98
106
|
defects = verification_command_defects(command)
|
|
99
107
|
if defects:
|
|
100
108
|
raise ValueError("; ".join(defects))
|
|
@@ -105,11 +113,14 @@ def capture_verification_target(
|
|
|
105
113
|
raise ValueError(f"verification target mismatch: expected HEAD {expected_head}, actual {head}, root {actual_root}")
|
|
106
114
|
files = source_content_snapshot(worktree, frozenset({".okstra"}))
|
|
107
115
|
digest = hashlib.sha256(json.dumps(files, sort_keys=True).encode()).hexdigest()
|
|
108
|
-
|
|
116
|
+
result = {
|
|
109
117
|
"schemaVersion": "1.0", "runManifest": str(manifest_path),
|
|
110
118
|
"taskKey": manifest["taskKey"], "cwd": str(worktree),
|
|
111
119
|
"head": head, "sourceDigest": digest, "command": command,
|
|
112
120
|
}
|
|
121
|
+
if declared_command != command:
|
|
122
|
+
result.update(declaredCommand=declared_command, commandAnnotation=annotation)
|
|
123
|
+
return result
|
|
113
124
|
|
|
114
125
|
|
|
115
126
|
def main(argv: list[str] | None = None) -> int:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: okstra-manager
|
|
3
|
-
description: Use when the user wants to manage okstra work across multiple project roots, register or discover projects under a manager, create or update a shared manager task, sync project child-task status into manager state, or launch a child task from manager context. Trigger words include "okstra manager", "okstra-manager", "multiple projects", "cross-project", "group projects together", "manager task".
|
|
3
|
+
description: Use when the user wants to manage okstra work across multiple project roots, register or discover projects under a manager, create or update a shared manager task, sync project child-task status into manager state, split a Linear project or issue into per-project scoped briefs, or launch a child task from manager context. Trigger words include "okstra manager", "okstra-manager", "multiple projects", "cross-project", "group projects together", "manager task", "split this Linear project", "brief per project".
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# OKSTRA Manager
|
|
@@ -28,19 +28,68 @@ okstra manager task sync --manager-id <manager-id> --task-group <task-group> --t
|
|
|
28
28
|
okstra manager task assign --manager-id <manager-id> --task-group <task-group> --task-id <task-id> --project-id <project-id> [--child-task-id <child-task-id>] [--role <role>] [--tag <tag>] [--assignment <text>]
|
|
29
29
|
okstra manager task note --manager-id <manager-id> --task-group <task-group> --task-id <task-id> --scope <shared|project> [--project-id <project-id>] --body <text>
|
|
30
30
|
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>]
|
|
31
|
+
okstra manager task split --manager-id <manager-id> --task-group <task-group> --task-id <task-id> --plan <split-plan.json> [--overwrite]
|
|
32
|
+
okstra manager list managers
|
|
33
|
+
okstra manager list projects --manager-id <manager-id>
|
|
34
|
+
okstra manager list tasks --manager-id <manager-id>
|
|
35
|
+
okstra manager view --manager-id <manager-id>
|
|
31
36
|
```
|
|
32
37
|
|
|
33
38
|
- Public child task identity is `project-id:task-group:task-id`.
|
|
34
39
|
- `okstra manager new task --task ...` should prefer the full child key form above. The CLI also accepts shorthand under the command's `--task-group`, but the full key is the public form to show users.
|
|
35
40
|
- When a manager task id differs from the actual child task id for a specific project, pass `--child-task-id <child-task-id>` to `task assign` and `task run`.
|
|
36
41
|
|
|
42
|
+
## Tracker Split
|
|
43
|
+
|
|
44
|
+
Use this when one Linear project, or one parent issue, covers work in several registered projects and each project needs its own brief. A Linear project can point at several projects, and one issue can too; every (issue, project) pair gets its own brief and child task.
|
|
45
|
+
|
|
46
|
+
1. Run `okstra manager list projects --manager-id <manager-id>`. These are the only valid targets. Register a missing repo with `okstra manager new project ...` first.
|
|
47
|
+
2. Fetch from the Linear MCP: the project (`get_project`) and its issues (`list_issues` filtered by that project), then each issue with `get_issue`, including sub-issues and blocking or related links. If the Linear tools are missing, load them once with `ToolSearch`; if they are still missing, ask the user to paste the bodies. Never invent ticket content.
|
|
48
|
+
3. For each issue, propose target projects from evidence: labels, team, repository names or paths in the body. Confirm with `AskUserQuestion` (multi-select, recommended projects first, reason in each description).
|
|
49
|
+
4. For each (issue, project) pair, draft the scope: the part of the issue this project implements. Confirm it with `AskUserQuestion` offering one or two drafts plus a custom-input option. A pair without scope is rejected by the CLI.
|
|
50
|
+
5. Author each pair's brief fields under the okstra-brief-gen brief contract: bodies verbatim, `EB-NNN` / `PB-NNN` / `EO-NNN` items as `<id> <observable condition> — verify: <how>`, anything without an observation method under `externalGates`, and `openQuestions` rows prefixed `general:`, `terminology:`, `intent-check:`, `conversion-block:` or `adr-candidate:`.
|
|
51
|
+
6. Write the plan JSON below with the Write tool to a scratch path outside the project roots, then run `okstra manager task split ... --plan <path>`.
|
|
52
|
+
- `split plan rejected` or `rendered briefs failed validate-brief`: fix the named fields and run it again. Nothing was written.
|
|
53
|
+
- `briefs already exist with different content`: show the listed paths and ask before adding `--overwrite`, which replaces hand edits. Adding issues to an earlier split changes every brief's Related Task Graph, so existing briefs are listed too.
|
|
54
|
+
7. Each `Brief N task key` is `<project-id>:<task-group>:<child-task-id>`. Start one with `okstra manager task run ... --project-id <project-id> --child-task-id <child-task-id>` and follow the Child Launch Rule. The packet already carries `--task-brief`, and `--task-type` until the child task exists.
|
|
55
|
+
|
|
56
|
+
Plan JSON (`schemaVersion` 1). `source.kind` is `project` or `issue`; `recommendedPhase` is `requirements-discovery` (default), `error-analysis` or `improvement-discovery`; `relations[].relation` uses the brief Related Task Graph values; list fields may be omitted.
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"schemaVersion": 1,
|
|
61
|
+
"source": {"kind": "project", "ref": "<Linear URL>", "title": "<title>", "fetchedVia": "<tool>", "fetchedAt": "YYYY-MM-DD HH:MM", "body": "<verbatim>"},
|
|
62
|
+
"issues": [
|
|
63
|
+
{
|
|
64
|
+
"ticketId": "LIN-12", "title": "<title>", "url": "<Linear URL>", "fetchedVia": "<tool>", "fetchedAt": "YYYY-MM-DD HH:MM", "body": "<verbatim>",
|
|
65
|
+
"relations": [{"relation": "blocked-by", "to": "LIN-11", "source": "<where the link came from>", "impact": "<what the next phase must keep>"}],
|
|
66
|
+
"assignments": [
|
|
67
|
+
{
|
|
68
|
+
"projectId": "<registered project id>", "scope": ["<work in this project>"], "outOfScope": [],
|
|
69
|
+
"recommendedPhase": "requirements-discovery",
|
|
70
|
+
"context": "<text>", "problem": "<text>", "desiredOutcome": "<text>",
|
|
71
|
+
"expectedBehavior": [], "preservedBehavior": [], "expectedOutcome": [],
|
|
72
|
+
"externalGates": [], "constraints": [], "openQuestions": []
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The CLI writes each brief to `<projectRoot>/.okstra/briefs/<task-group>/<ticketId>-<file-title>.md` with a `## Project Scope` section: this project's scope, `outOfScope`, and the scope of every other project the same issue went to. It validates every brief with the brief validator before writing any file, registers the child tasks, and keeps the plan at `split-plan.json` in the manager task directory.
|
|
81
|
+
|
|
82
|
+
## Overview Page
|
|
83
|
+
|
|
84
|
+
When the user wants to see a manager at a glance, run `okstra manager task sync ...` for each task listed by `okstra manager list tasks ...` whose snapshot must be current, then run `okstra manager view --manager-id <manager-id>` and give the user the returned `View URL`. The page reads manager files only, so each task shows the state of its last sync and prints its own sync command.
|
|
85
|
+
|
|
37
86
|
## Child Launch Rule
|
|
38
87
|
|
|
39
88
|
For launching child work:
|
|
40
89
|
|
|
41
90
|
1. Run `okstra manager task sync ...` if the manager snapshot must be refreshed first.
|
|
42
91
|
2. Run `okstra manager task run ...`.
|
|
43
|
-
3. Read the returned fixed fields `Backend`, `Worker dispatch backend`, `Project root`, `Context path`,
|
|
44
|
-
4.
|
|
92
|
+
3. Read the returned fixed fields `Backend`, `Worker dispatch backend`, `Project root`, `Context path`, `Run command`, every numbered `Run arg N`, and `Shell command`.
|
|
93
|
+
4. Give the user the `Shell command` line verbatim and tell them to run it in a new terminal. It starts the installed launcher in the child project: the launcher fills the task type and brief from the child task manifest or asks for them, prepares the run with the manager context directive, and starts the child lead as a separate host process (`Backend` `spawn-process`).
|
|
45
94
|
|
|
46
|
-
|
|
95
|
+
Do not run the `Shell command` through this session's Bash tool: it opens an interactive host session. Do not rebuild it from `Run arg N` or swap in `okstra run`, which drops the task inputs and the directive for a lead host.
|
|
@@ -244,6 +244,8 @@ Output: `{ok: true, outcome: {renderArgv: ["--lead-runtime", "...", ...], render
|
|
|
244
244
|
|
|
245
245
|
`userAuthorization`, when present, preserves the confirmation prompt actually emitted by this wizard and the relayed `proceed` response. Its `--user-authorization-json` token carries that record into the run manifest; preserve it unchanged. Do not synthesize a receipt for older confirmed states. Include the translator's disclosed provider/model and report material when citing this record in host execution requests, including `report-finalize`. The record documents task scope; it does not grant host execution privileges.
|
|
246
246
|
|
|
247
|
+
Before retrying a worker in an existing run, inspect its last invocation attempt: await running work; append an attempt only after `failed-no-mutation` within the retry budget. Preserve every other terminal attempt. `okstra team dispatch` routes the legacy declaration-only implementer failure to a separate same-model evidence-recovery invocation; collect its result before independent verification. Other terminal failures need `okstra agent-prompt materialize` with a new `--invocation-id`, distinct `--prompt`/`--result` paths, and unchanged assignment/model for a bounded correction. Do not restart completed implementation or request renewed approval for unchanged scope. Enforcement: `dispatch_core._next_attempt` and `execution_manifest._validate_next_attempt`.
|
|
248
|
+
|
|
247
249
|
Run every `outcome.persistActions[]` entry BEFORE `render-bundle`. The only supported action is:
|
|
248
250
|
|
|
249
251
|
```json
|
|
@@ -489,6 +491,6 @@ Follow the rendered launch prompt's "Progress, remaining work, and recommendatio
|
|
|
489
491
|
## Output Rules
|
|
490
492
|
|
|
491
493
|
- Echo each captured answer (`result.echo`) on one short line so the user sees what was registered.
|
|
492
|
-
- Name every file you show the user as a markdown link — `[<
|
|
494
|
+
- Name every file you show the user as a markdown link — `[<short label>](<absolute path>)`, with the absolute path inside the parentheses and a short label naming what the file is. A relative destination does not open from an external program. That is the only form the host renders as clickable; a path in backticks is text the user has to copy out. The `report-finalize` result's `reportPaths.markdown` carries the run's report, report record, and team state already in that form. Commands stay in backticks — a link is for a file, not for something to run.
|
|
493
495
|
- Never invent identity; if a `text` prompt returns an empty answer where the wizard rejects it, the user must retry.
|
|
494
496
|
- After Step 6, begin the lead workflow without re-summarizing the skill itself. For a single run, finish after Step 6 and any same-run recovery, unless the user has already authorized continuing the task through further phases. In an unattended chain where `orchestration.chainStages` has 2+ elements, repeat Step 6 per stage until Step 7's queue is empty (or it stops at a "not ready" / exception gate), then finish. When `report-finalize` returns `recovery.mode: same-run`, continue the authorized corrections in this run and execute `recovery.resumeCommand` before closeout; preserve approvals and model choices without reopening the wizard. The command and owner issues are supplied by `report_finalize._finalize_recovery`. When the lead (or this skill, after the lead returns) reports a successfully finalized run over, close with the user's next action — one command they can run now. A prohibition is not a next action. Take the pointer from the `report-finalize` result's top-level `nextRecommendedPhase` (`phase`, `status`, `rationale`; also on stderr as `next phase status:` / `next phase:` / `next phase rationale:`) — do not re-derive it from the report, and treat a `nextRecommendedPhaseError` as "pointer unreadable", said in one line before the `validate-run` branch. The same result also carries `nextCommand` — `{command, note}`, the table below already applied to this run. When `command` is non-empty it is the close; when it is empty the `note` says what to do with the `rationale` instead. After `implementation-planning`, open `blocks: approval` rows → `/okstra-user-response`. A recorded `accept-risk` / `select` / `answer` is not an open blocker. No open approval blocker → `/okstra-run` → `implementation` or `--approve` (do not start another planning run; do not say `/okstra-inspect`). For every other task type, quote the pointer's `rationale` in every branch — that sentence is the report's own reason and it is what the user asked to be analysed. Pointer `status: ready` → `/okstra-run` for that phase; `status: terminal` → say the task is finished, name any follow-up tasks this run registered, and do not say `/okstra-inspect`; `status: blocked` → issue the command the `rationale` calls for (`/okstra-user-response` for the `C-NNN` ids, `/okstra-run` for the phase it names); `validate-run` failed with `recovery.mode: phase-reentry` → name the cause and use `nextCommand` for the recorded earlier phase; otherwise `/okstra-inspect status`.
|
|
@@ -140,6 +140,15 @@ Then create the file — paste the literal `projectRoot` from Step 2 and the lit
|
|
|
140
140
|
okstra setup --yes --project-root /abs/path/to/projectRoot --project-id my-project-id
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
+
`okstra setup` also refreshes the okstra-managed citation-guidance block in
|
|
144
|
+
`<PROJECT_ROOT>/CLAUDE.md` and `AGENTS.md` when those files already exist (it never
|
|
145
|
+
creates them). The block tells agents not to carry okstra-internal references — report
|
|
146
|
+
section numbers, `C-NNN` clarification ids, run/stage ids, `.okstra/...` paths — into
|
|
147
|
+
writing that is not an okstra report, where the reader cannot resolve them. The command
|
|
148
|
+
reports the files it touched in its JSON `citationGuidance` array; a failure there is a
|
|
149
|
+
`warning:` line, not a non-zero exit. Tell the user which guidance files were updated so
|
|
150
|
+
they can review the appended block.
|
|
151
|
+
|
|
143
152
|
## Step 3.5 (optional): project customisation
|
|
144
153
|
|
|
145
154
|
The built-in defaults work for most projects — skip straight to Step 4
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>{{TITLE}}</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--bg: #f7f7f5;
|
|
10
|
+
--panel: #ffffff;
|
|
11
|
+
--text: #1f2328;
|
|
12
|
+
--muted: #5f6670;
|
|
13
|
+
--line: #d9dbde;
|
|
14
|
+
--planned: #6e7781;
|
|
15
|
+
--missing: #9a6700;
|
|
16
|
+
--running: #0969da;
|
|
17
|
+
--done: #1a7f37;
|
|
18
|
+
--blocked: #cf222e;
|
|
19
|
+
}
|
|
20
|
+
@media (prefers-color-scheme: dark) {
|
|
21
|
+
:root {
|
|
22
|
+
--bg: #111418;
|
|
23
|
+
--panel: #1a1f25;
|
|
24
|
+
--text: #e6e8eb;
|
|
25
|
+
--muted: #9aa3ad;
|
|
26
|
+
--line: #2f363e;
|
|
27
|
+
--planned: #8b949e;
|
|
28
|
+
--missing: #d29922;
|
|
29
|
+
--running: #58a6ff;
|
|
30
|
+
--done: #3fb950;
|
|
31
|
+
--blocked: #f85149;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
* { box-sizing: border-box; }
|
|
35
|
+
body {
|
|
36
|
+
margin: 0;
|
|
37
|
+
background: var(--bg);
|
|
38
|
+
color: var(--text);
|
|
39
|
+
font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", "Apple SD Gothic Neo", "Noto Sans KR", sans-serif;
|
|
40
|
+
}
|
|
41
|
+
main { max-width: 1200px; margin: 0 auto; padding: 24px 16px 48px; }
|
|
42
|
+
header h1 { margin: 0 0 4px; font-size: 22px; }
|
|
43
|
+
.meta { color: var(--muted); font-size: 13px; }
|
|
44
|
+
section { margin-top: 28px; }
|
|
45
|
+
h2 { font-size: 17px; margin: 0 0 10px; }
|
|
46
|
+
h3 { font-size: 15px; margin: 0; }
|
|
47
|
+
.panel {
|
|
48
|
+
background: var(--panel);
|
|
49
|
+
border: 1px solid var(--line);
|
|
50
|
+
border-radius: 8px;
|
|
51
|
+
padding: 16px;
|
|
52
|
+
margin-bottom: 16px;
|
|
53
|
+
}
|
|
54
|
+
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px; }
|
|
55
|
+
.tile { background: var(--panel); border: 1px solid var(--line); border-radius: 8px; padding: 10px 12px; }
|
|
56
|
+
.tile .count { font-size: 24px; font-weight: 600; }
|
|
57
|
+
.tile .label { color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em; }
|
|
58
|
+
.scroll { overflow-x: auto; }
|
|
59
|
+
table { width: 100%; border-collapse: collapse; }
|
|
60
|
+
th, td { text-align: left; padding: 7px 10px; border-bottom: 1px solid var(--line); vertical-align: top; }
|
|
61
|
+
th { color: var(--muted); font-weight: 600; font-size: 12px; white-space: nowrap; }
|
|
62
|
+
.wrap { display: inline-block; min-width: 220px; }
|
|
63
|
+
code.id { white-space: nowrap; word-break: normal; }
|
|
64
|
+
code { font: 12px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; word-break: break-all; }
|
|
65
|
+
.chip {
|
|
66
|
+
display: inline-block;
|
|
67
|
+
padding: 1px 8px;
|
|
68
|
+
border-radius: 999px;
|
|
69
|
+
border: 1px solid currentColor;
|
|
70
|
+
font-size: 12px;
|
|
71
|
+
white-space: nowrap;
|
|
72
|
+
}
|
|
73
|
+
.chip.planned { color: var(--planned); }
|
|
74
|
+
.chip.missing { color: var(--missing); }
|
|
75
|
+
.chip.running { color: var(--running); }
|
|
76
|
+
.chip.done { color: var(--done); }
|
|
77
|
+
.chip.blocked { color: var(--blocked); }
|
|
78
|
+
.task-head { display: flex; flex-wrap: wrap; gap: 8px 16px; align-items: baseline; margin-bottom: 8px; }
|
|
79
|
+
.counts { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
80
|
+
.hint { color: var(--muted); font-size: 12px; margin: 6px 0 12px; }
|
|
81
|
+
details { margin-top: 10px; }
|
|
82
|
+
summary { cursor: pointer; color: var(--muted); }
|
|
83
|
+
ul.plain { margin: 6px 0 0; padding-left: 18px; }
|
|
84
|
+
.empty { color: var(--muted); }
|
|
85
|
+
a { color: var(--running); }
|
|
86
|
+
</style>
|
|
87
|
+
</head>
|
|
88
|
+
<body>
|
|
89
|
+
<main>
|
|
90
|
+
<header>
|
|
91
|
+
<h1>{{TITLE}}</h1>
|
|
92
|
+
<div class="meta">{{META}}</div>
|
|
93
|
+
</header>
|
|
94
|
+
<section>
|
|
95
|
+
<h2>Summary</h2>
|
|
96
|
+
{{SUMMARY}}
|
|
97
|
+
</section>
|
|
98
|
+
<section>
|
|
99
|
+
<h2>Projects</h2>
|
|
100
|
+
{{PROJECTS}}
|
|
101
|
+
</section>
|
|
102
|
+
<section>
|
|
103
|
+
<h2>Manager tasks</h2>
|
|
104
|
+
{{TASKS}}
|
|
105
|
+
</section>
|
|
106
|
+
</main>
|
|
107
|
+
</body>
|
|
108
|
+
</html>
|
|
@@ -371,6 +371,8 @@
|
|
|
371
371
|
"final-verification": {
|
|
372
372
|
"verification-verdict": "Verification verdict",
|
|
373
373
|
"result-per-requirement": "Result per requirement",
|
|
374
|
+
"requirement": "Requirement",
|
|
375
|
+
"evidence": "Evidence",
|
|
374
376
|
"what-the-change-added": "What the change added",
|
|
375
377
|
"traced-to": "Serves",
|
|
376
378
|
"callers": "Callers",
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
{% extends "html/base.template.html" %}
|
|
2
2
|
{% from "html/macros/layout.html" import narrative as render_narrative, row_key %}
|
|
3
|
-
{% from "html/macros/visualizations.html" import figure %}
|
|
4
3
|
|
|
5
4
|
{% block human_content %}
|
|
6
5
|
<section data-report-section="verification-verdict">
|
|
@@ -22,7 +21,8 @@
|
|
|
22
21
|
<section data-report-section="requirement-results" data-report-field="finalVerification.validationEvidence">
|
|
23
22
|
<h2>{{ t('tasks.final-verification.result-per-requirement') }}</h2>
|
|
24
23
|
{{ render_narrative(narrative.coverageExplanation, "finalVerification.userNarrative.coverageExplanation") }}
|
|
25
|
-
{{
|
|
24
|
+
<div class="table-scroll"><table><thead><tr><th>{{ t('macros.layout.id') }}</th><th>{{ t('tasks.final-verification.requirement') }}</th><th>{{ t('macros.layout.status') }}</th><th>{{ t('tasks.final-verification.evidence') }}</th></tr></thead>
|
|
25
|
+
<tbody>{% for row in final.validationEvidence %}<tr id="id-{{ row.id }}"><td>{{ row.id | inline_code }}</td><td>{{ row.requirement | inline_code }}</td><td><span class="status status-{{ row.status }}">{{ row.status }}</span></td><td>{{ row.artifact | inline_code }}</td></tr>{% endfor %}</tbody></table></div>
|
|
26
26
|
</section>
|
|
27
27
|
|
|
28
28
|
<section data-report-section="added-surface" data-report-field="finalVerification.addedSurfaceAudit">
|
|
@@ -104,6 +104,10 @@ AUGMENTATION_LABELS = {
|
|
|
104
104
|
|
|
105
105
|
REPORTER_CONFIRMATION_VALUES = {"complete", "partial", "pending", "skipped"}
|
|
106
106
|
|
|
107
|
+
# okstra-manager `task split` renders the same brief contract for each project
|
|
108
|
+
# it splits a tracker issue into.
|
|
109
|
+
GENERATORS = {"okstra-brief-gen", "okstra-manager"}
|
|
110
|
+
|
|
107
111
|
SCOPE_VALUES = {"reporter-input", "codebase"}
|
|
108
112
|
|
|
109
113
|
TASK_GRAPH_HEADER = ["From", "Relation", "To", "Direction", "Source", "Impact"]
|
|
@@ -693,9 +697,10 @@ def validate_brief(path: Path, briefs_root: Path) -> list[str]:
|
|
|
693
697
|
if fm.get("type") != "brief":
|
|
694
698
|
errors.append(f"frontmatter type must be 'brief', got {fm.get('type')!r}")
|
|
695
699
|
|
|
696
|
-
if fm.get("generator")
|
|
700
|
+
if fm.get("generator") not in GENERATORS:
|
|
697
701
|
errors.append(
|
|
698
|
-
f"frontmatter generator must be
|
|
702
|
+
f"frontmatter generator must be one of {sorted(GENERATORS)}, "
|
|
703
|
+
f"got {fm.get('generator')!r}"
|
|
699
704
|
)
|
|
700
705
|
|
|
701
706
|
if fm.get("reporter-confirmations") not in REPORTER_CONFIRMATION_VALUES:
|
|
@@ -5443,19 +5443,8 @@ def _validate_plan_body_state_rounds(
|
|
|
5443
5443
|
)
|
|
5444
5444
|
|
|
5445
5445
|
|
|
5446
|
-
def
|
|
5447
|
-
"""
|
|
5448
|
-
|
|
5449
|
-
입력 템플릿의 §"Forbidden In This Run" 은 계획 목록 밖에서 건드린 파일을 전부
|
|
5450
|
-
`Out-of-plan edits` 에 적으라고 요구한다. 그 목록이 실제 diff 와 무관하면 기록은
|
|
5451
|
-
검토자에게 성실해 보이는 문자열일 뿐이다 — 실제로 바꾼 적 없는 파일이 적혀
|
|
5452
|
-
있으면 나머지 행도 믿을 근거가 없다.
|
|
5453
|
-
|
|
5454
|
-
계획 목록 자체와의 대조(계획 밖 파일이 여기 빠졌는지)는 하지 않는다. 그러려면
|
|
5455
|
-
`approvedPlanReference.planFile` 이 가리키는 다른 리포트를 열어 stage 범위까지
|
|
5456
|
-
풀어야 하고, 그 경로 해소를 검증할 실물 run 이 저장소에 없다. 여기서는 리포트
|
|
5457
|
-
안에서 닫히는 방향만 본다.
|
|
5458
|
-
"""
|
|
5446
|
+
def _warn_out_of_plan_edits_not_in_diff(data: dict, warnings: list[str]) -> None:
|
|
5447
|
+
"""소스 차이 목록은 별도 QA 산출물의 변경 여부를 증명하지 못한다."""
|
|
5459
5448
|
implementation = data.get("implementation")
|
|
5460
5449
|
if not isinstance(implementation, dict):
|
|
5461
5450
|
return
|
|
@@ -5470,7 +5459,7 @@ def _validate_out_of_plan_edits_are_real(data: dict, failures: list[str]) -> Non
|
|
|
5470
5459
|
continue
|
|
5471
5460
|
target = row.get("file")
|
|
5472
5461
|
if isinstance(target, str) and target and target not in changed:
|
|
5473
|
-
|
|
5462
|
+
warnings.append(
|
|
5474
5463
|
f"out-of-plan-edit: {row.get('id') or 'OOP-???'} 가 `{target}` 을 "
|
|
5475
5464
|
"계획 밖 편집으로 신고했지만 diffSummary 에 그 파일이 없다"
|
|
5476
5465
|
)
|
|
@@ -9755,7 +9744,10 @@ def main() -> int:
|
|
|
9755
9744
|
_validate_verifier_command_log_is_read_only(validation_data, failures)
|
|
9756
9745
|
_validate_verifier_reran_independently(validation_data, failures)
|
|
9757
9746
|
_validate_verifier_discrepancy_is_not_passed(validation_data, failures)
|
|
9758
|
-
|
|
9747
|
+
declaration_warnings: list[str] = []
|
|
9748
|
+
_warn_out_of_plan_edits_not_in_diff(validation_data, declaration_warnings)
|
|
9749
|
+
for warning in declaration_warnings:
|
|
9750
|
+
print(f"validate-run: warning: {warning}", file=sys.stderr)
|
|
9759
9751
|
if task_type == "implementation-planning":
|
|
9760
9752
|
_validate_plan_body_state_file(
|
|
9761
9753
|
validation_data,
|