create-agdf 0.10.2 → 0.10.3

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.
Files changed (28) hide show
  1. package/generated/.github/skills/agdf-gate-check/SKILL.md +11 -18
  2. package/generated/.github/skills/agdf-interaction-locales.json +8 -0
  3. package/generated/.github/skills/agdf-release-or/SKILL.md +1 -1
  4. package/generated/.github/skills/contracts/interaction.md +23 -0
  5. package/generated/.opencode/agdf-interaction-locales.json +8 -0
  6. package/generated/.opencode/contracts/interaction.md +23 -0
  7. package/generated/.opencode/skills/agdf-gate-check/SKILL.md +11 -18
  8. package/generated/.opencode/skills/agdf-release-or/SKILL.md +1 -1
  9. package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
  10. package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
  11. package/generated/plugins/agdf/meta/agdf-interaction-locales.json +8 -0
  12. package/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
  13. package/generated/plugins/agdf/meta/contracts/interaction.md +23 -0
  14. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-interaction-locales.json +8 -0
  15. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
  16. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/validation-handlers.js +2 -1
  17. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/gate-check.js +16 -25
  18. package/generated/plugins/agdf/runtime/create-agdf/lib/interaction-presentation.js +80 -0
  19. package/generated/plugins/agdf/runtime/create-agdf/package.json +1 -1
  20. package/generated/plugins/agdf/runtime/runtime-manifest.json +2 -2
  21. package/generated/plugins/agdf/scripts/check-runtime-integrity.mjs +20 -5
  22. package/generated/plugins/agdf/skills/gate-check/SKILL.md +11 -18
  23. package/generated/plugins/agdf/skills/release-or/SKILL.md +1 -1
  24. package/lib/cli/validation-handlers.js +2 -1
  25. package/lib/control-evaluation/gate-check.js +16 -25
  26. package/lib/installers/opencode.js +52 -4
  27. package/lib/interaction-presentation.js +80 -0
  28. package/package.json +1 -1
@@ -85,10 +85,11 @@ This skill owns only these operational responsibilities:
85
85
  5. Revalidate the same run, gate and revision immediately after the response and before persistence.
86
86
  6. Persist only a currently valid exact approval through the existing control-state workflow.
87
87
 
88
- For status, blocked, read-only or rationale interactions, consume the same canonical projection and
89
- locale contract without asking for approval. Internal process steps remain distinct from user
90
- decisions. If the projection, adapter evidence or revalidation is missing, follow the contract's
91
- fail-closed outcome and leave authority unchanged.
88
+ For status, blocked, read-only or rationale interactions, consume the canonical
89
+ `status_presentation.markdown` verbatim and apply the locale contract without asking for approval.
90
+ Do not reconstruct its fields, ordering, labels or Markdown from `status_card` JSON. Internal process
91
+ steps remain distinct from user decisions. If the projection, adapter evidence or revalidation is
92
+ missing, follow the contract's fail-closed outcome and leave authority unchanged.
92
93
 
93
94
  ## Rules
94
95
  1. Fail closed when a required approval or artefact status is missing.
@@ -163,23 +164,15 @@ If a status is not explicit, do not assume it is satisfied.
163
164
  12. If branch, workspace and durable artefacts disagree, do not choose a scope silently; report the ambiguity and ask for the smallest clarifying gate action.
164
165
 
165
166
  ## Output
166
- Keep the result short and operational. Render the Runtime Contract's compact human-facing Run Status Card rather than a surface-specific summary:
167
-
168
- | Run status | Value |
169
- |---|---|
170
- | Status | `open | blocked` |
171
- | Current gate | `<GateName or internal step>` |
172
- | Allowed now | `<currently allowed outputs>` |
173
- | Blocked by | `<blocking condition or none>` |
174
- | Missing approval | `Approval: <GateName> | none` |
175
- | Next step | `<single permissible next step>` |
176
- | Quality outlook | `<next meaningful quality focus or none>` |
177
-
178
- When an approval is missing, also include `Next gate after approval` and `Allowed after approval` exactly as constrained by the Runtime Contract. Keep forbidden outputs, evidence and next-skill detail in the concise surrounding text when relevant; they are not extra status-card rows.
167
+ Keep the result short and operational. For a status-only response, consume the Runtime Contract's
168
+ code-owned `status_presentation.markdown` verbatim. It is the compact localized operational Run
169
+ Status Card and already includes current and post-approval authority, next-step and quality fields.
170
+ Keep full raw evidence in `status_card` JSON or concise surrounding detail when relevant; do not turn
171
+ the primary chat card into an audit dump. Do not maintain or render a skill-local table template.
179
172
 
180
173
  This complete operational status table remains the status-reporting and detail
181
174
  surface. When the same response immediately requests a ready gate approval,
182
- render its six-field compact approval-time projection first, then the Gate
175
+ render its five-field compact approval-time projection first, then the Gate
183
176
  Transition Card in the same immediately preceding assistant message, then
184
177
  invoke exactly one native question or exact-text fallback. Both cards derive
185
178
  from the same snapshot and are shown exactly once.
@@ -11,6 +11,9 @@
11
11
  "en": {
12
12
  "statusCard": {
13
13
  "title": "AGDF status-card",
14
+ "status": "Status",
15
+ "field": "Status field",
16
+ "value": "Value",
14
17
  "run": "Selected run",
15
18
  "humanTitle": "Run title",
16
19
  "gate": "Current gate",
@@ -107,6 +110,7 @@
107
110
  "exactTextRequest": "To approve, reply exactly with `{approval}`; otherwise request revision or decline.",
108
111
  "decisionFollows": "will be the next user decision",
109
112
  "presentationFailure": "Approval cards could not be rendered safely.",
113
+ "statusPresentationFailure": "Status card could not render safely.",
110
114
  "nonReadyDecision": "No decision requested: {reason}.",
111
115
  "approveDescription": "Approve this gate with the exact authorization value.",
112
116
  "reviseLabel": "Revise",
@@ -214,6 +218,9 @@
214
218
  "de": {
215
219
  "statusCard": {
216
220
  "title": "AGDF-Statuskarte",
221
+ "status": "Status",
222
+ "field": "Statusfeld",
223
+ "value": "Wert",
217
224
  "run": "Ausgewählter Run",
218
225
  "humanTitle": "Run-Titel",
219
226
  "gate": "Aktuelles Gate",
@@ -310,6 +317,7 @@
310
317
  "exactTextRequest": "Zur Freigabe exakt mit `{approval}` antworten; andernfalls Überarbeitung anfordern oder ablehnen.",
311
318
  "decisionFollows": "ist die nächste Nutzerentscheidung",
312
319
  "presentationFailure": "Die Freigabekarten konnten nicht sicher dargestellt werden.",
320
+ "statusPresentationFailure": "Statuskarte nicht sicher darstellbar.",
313
321
  "nonReadyDecision": "Keine Entscheidung angefordert: {reason}.",
314
322
  "approveDescription": "Dieses Gate mit dem exakten Freigabewert autorisieren.",
315
323
  "reviseLabel": "Überarbeiten",
@@ -52,7 +52,7 @@ Use the Runtime Contract definition of `Relevant Run` to decide whether OR is ma
52
52
  13. Keep machine status normalization in the CLI; do not write internal snake_case status values into the human-facing backlog.
53
53
  14. Reconcile Context Graph impact before clean closeout: report `context_graph_reconciliation: resolved | not_applicable | open_gap`.
54
54
  15. If Context Graph work remains unresolved, report it as an explicit open gap and do not describe the run as cleanly handoff-ready.
55
- 15. After writing or updating `MASTER_BACKLOG.md`, run `doctor --json` (or the locally available equivalent) and resolve any `AGDF_BACKLOG_STATUS_UNKNOWN` or `AGDF_BACKLOG_ARTEFACT_LABEL_UNKNOWN` finding before closeout. See the AGDF control scaffold's `MASTER_BACKLOG.md` template Rules section for the canonical status/artefact label vocabulary.
55
+ 16. After writing or updating `MASTER_BACKLOG.md`, run `doctor --json` (or the locally available equivalent) and resolve any `AGDF_BACKLOG_STATUS_UNKNOWN` or `AGDF_BACKLOG_ARTEFACT_LABEL_UNKNOWN` finding before closeout. See the AGDF control scaffold's `MASTER_BACKLOG.md` template Rules section for the canonical status/artefact label vocabulary.
56
56
 
57
57
  ## When To Use
58
58
  - at the end of every relevant run
@@ -40,6 +40,29 @@ It must not introduce a second gate model or override `agdf-gate-check`, `delive
40
40
 
41
41
  `next_gate_after_approval` and `allowed_after_approval` must be `none` when no approval is missing, when the current step is internal, or when the run is in OR/completed handoff. They must not imply implementation, QA, release, commit, push or PR authority unless the existing gate model already allows that authority.
42
42
 
43
+ ### Deterministic Operational Presentation
44
+
45
+ `status_card` is the canonical machine/audit projection. The same evaluation also exposes an additive
46
+ `status_presentation` object with selected run/revision/gate/locale identity, `semantic_block:
47
+ run_status_card`, one compact Markdown block with localized labels and canonical evaluated values,
48
+ and `authorizes: false`. The full raw and evidence-bearing projection remains available unchanged in
49
+ `status_card` JSON for audit and automation; it is not duplicated into the primary chat card.
50
+ Fresh legacy-compatible control state without a durable revision uses the explicit non-authorizing
51
+ identity `unversioned`; ready approval presentation remains impossible without a real revision.
52
+
53
+ `interaction-presentation.js` is the only owner that may select, label, order, localize and render the
54
+ operational status fields. The gate-check CLI, skills and surface adapters consume
55
+ `status_presentation.markdown` verbatim. They may add concise surrounding explanation, but they must
56
+ not reconstruct a table, omit or rename fields, substitute generic gate actions for evaluated
57
+ `allowed_now`/`forbidden_now`, or maintain another status-card template. If the projection is missing
58
+ or its selected run, revision, gate or locale identity is stale, presentation fails closed rather
59
+ than falling back to model-generated status Markdown.
60
+
61
+ This determinism protects semantic parity, permission boundaries and evidence visibility across
62
+ hosts. It does not standardize host chrome or unrelated conversational prose. `approval_presentation`
63
+ remains the separate decision-time projection and is present only for a ready user gate; both public
64
+ presentations derive from the same `status_card` snapshot and neither grants approval.
65
+
43
66
 
44
67
  ### Breadcrumb
45
68
 
@@ -11,6 +11,9 @@
11
11
  "en": {
12
12
  "statusCard": {
13
13
  "title": "AGDF status-card",
14
+ "status": "Status",
15
+ "field": "Status field",
16
+ "value": "Value",
14
17
  "run": "Selected run",
15
18
  "humanTitle": "Run title",
16
19
  "gate": "Current gate",
@@ -107,6 +110,7 @@
107
110
  "exactTextRequest": "To approve, reply exactly with `{approval}`; otherwise request revision or decline.",
108
111
  "decisionFollows": "will be the next user decision",
109
112
  "presentationFailure": "Approval cards could not be rendered safely.",
113
+ "statusPresentationFailure": "Status card could not render safely.",
110
114
  "nonReadyDecision": "No decision requested: {reason}.",
111
115
  "approveDescription": "Approve this gate with the exact authorization value.",
112
116
  "reviseLabel": "Revise",
@@ -214,6 +218,9 @@
214
218
  "de": {
215
219
  "statusCard": {
216
220
  "title": "AGDF-Statuskarte",
221
+ "status": "Status",
222
+ "field": "Statusfeld",
223
+ "value": "Wert",
217
224
  "run": "Ausgewählter Run",
218
225
  "humanTitle": "Run-Titel",
219
226
  "gate": "Aktuelles Gate",
@@ -310,6 +317,7 @@
310
317
  "exactTextRequest": "Zur Freigabe exakt mit `{approval}` antworten; andernfalls Überarbeitung anfordern oder ablehnen.",
311
318
  "decisionFollows": "ist die nächste Nutzerentscheidung",
312
319
  "presentationFailure": "Die Freigabekarten konnten nicht sicher dargestellt werden.",
320
+ "statusPresentationFailure": "Statuskarte nicht sicher darstellbar.",
313
321
  "nonReadyDecision": "Keine Entscheidung angefordert: {reason}.",
314
322
  "approveDescription": "Dieses Gate mit dem exakten Freigabewert autorisieren.",
315
323
  "reviseLabel": "Überarbeiten",
@@ -40,6 +40,29 @@ It must not introduce a second gate model or override `agdf-gate-check`, `delive
40
40
 
41
41
  `next_gate_after_approval` and `allowed_after_approval` must be `none` when no approval is missing, when the current step is internal, or when the run is in OR/completed handoff. They must not imply implementation, QA, release, commit, push or PR authority unless the existing gate model already allows that authority.
42
42
 
43
+ ### Deterministic Operational Presentation
44
+
45
+ `status_card` is the canonical machine/audit projection. The same evaluation also exposes an additive
46
+ `status_presentation` object with selected run/revision/gate/locale identity, `semantic_block:
47
+ run_status_card`, one compact Markdown block with localized labels and canonical evaluated values,
48
+ and `authorizes: false`. The full raw and evidence-bearing projection remains available unchanged in
49
+ `status_card` JSON for audit and automation; it is not duplicated into the primary chat card.
50
+ Fresh legacy-compatible control state without a durable revision uses the explicit non-authorizing
51
+ identity `unversioned`; ready approval presentation remains impossible without a real revision.
52
+
53
+ `interaction-presentation.js` is the only owner that may select, label, order, localize and render the
54
+ operational status fields. The gate-check CLI, skills and surface adapters consume
55
+ `status_presentation.markdown` verbatim. They may add concise surrounding explanation, but they must
56
+ not reconstruct a table, omit or rename fields, substitute generic gate actions for evaluated
57
+ `allowed_now`/`forbidden_now`, or maintain another status-card template. If the projection is missing
58
+ or its selected run, revision, gate or locale identity is stale, presentation fails closed rather
59
+ than falling back to model-generated status Markdown.
60
+
61
+ This determinism protects semantic parity, permission boundaries and evidence visibility across
62
+ hosts. It does not standardize host chrome or unrelated conversational prose. `approval_presentation`
63
+ remains the separate decision-time projection and is present only for a ready user gate; both public
64
+ presentations derive from the same `status_card` snapshot and neither grants approval.
65
+
43
66
 
44
67
  ### Breadcrumb
45
68
 
@@ -85,10 +85,11 @@ This skill owns only these operational responsibilities:
85
85
  5. Revalidate the same run, gate and revision immediately after the response and before persistence.
86
86
  6. Persist only a currently valid exact approval through the existing control-state workflow.
87
87
 
88
- For status, blocked, read-only or rationale interactions, consume the same canonical projection and
89
- locale contract without asking for approval. Internal process steps remain distinct from user
90
- decisions. If the projection, adapter evidence or revalidation is missing, follow the contract's
91
- fail-closed outcome and leave authority unchanged.
88
+ For status, blocked, read-only or rationale interactions, consume the canonical
89
+ `status_presentation.markdown` verbatim and apply the locale contract without asking for approval.
90
+ Do not reconstruct its fields, ordering, labels or Markdown from `status_card` JSON. Internal process
91
+ steps remain distinct from user decisions. If the projection, adapter evidence or revalidation is
92
+ missing, follow the contract's fail-closed outcome and leave authority unchanged.
92
93
 
93
94
  ## Rules
94
95
  1. Fail closed when a required approval or artefact status is missing.
@@ -163,23 +164,15 @@ If a status is not explicit, do not assume it is satisfied.
163
164
  12. If branch, workspace and durable artefacts disagree, do not choose a scope silently; report the ambiguity and ask for the smallest clarifying gate action.
164
165
 
165
166
  ## Output
166
- Keep the result short and operational. Render the Runtime Contract's compact human-facing Run Status Card rather than a surface-specific summary:
167
-
168
- | Run status | Value |
169
- |---|---|
170
- | Status | `open | blocked` |
171
- | Current gate | `<GateName or internal step>` |
172
- | Allowed now | `<currently allowed outputs>` |
173
- | Blocked by | `<blocking condition or none>` |
174
- | Missing approval | `Approval: <GateName> | none` |
175
- | Next step | `<single permissible next step>` |
176
- | Quality outlook | `<next meaningful quality focus or none>` |
177
-
178
- When an approval is missing, also include `Next gate after approval` and `Allowed after approval` exactly as constrained by the Runtime Contract. Keep forbidden outputs, evidence and next-skill detail in the concise surrounding text when relevant; they are not extra status-card rows.
167
+ Keep the result short and operational. For a status-only response, consume the Runtime Contract's
168
+ code-owned `status_presentation.markdown` verbatim. It is the compact localized operational Run
169
+ Status Card and already includes current and post-approval authority, next-step and quality fields.
170
+ Keep full raw evidence in `status_card` JSON or concise surrounding detail when relevant; do not turn
171
+ the primary chat card into an audit dump. Do not maintain or render a skill-local table template.
179
172
 
180
173
  This complete operational status table remains the status-reporting and detail
181
174
  surface. When the same response immediately requests a ready gate approval,
182
- render its six-field compact approval-time projection first, then the Gate
175
+ render its five-field compact approval-time projection first, then the Gate
183
176
  Transition Card in the same immediately preceding assistant message, then
184
177
  invoke exactly one native question or exact-text fallback. Both cards derive
185
178
  from the same snapshot and are shown exactly once.
@@ -52,7 +52,7 @@ Use the Runtime Contract definition of `Relevant Run` to decide whether OR is ma
52
52
  13. Keep machine status normalization in the CLI; do not write internal snake_case status values into the human-facing backlog.
53
53
  14. Reconcile Context Graph impact before clean closeout: report `context_graph_reconciliation: resolved | not_applicable | open_gap`.
54
54
  15. If Context Graph work remains unresolved, report it as an explicit open gap and do not describe the run as cleanly handoff-ready.
55
- 15. After writing or updating `MASTER_BACKLOG.md`, run `doctor --json` (or the locally available equivalent) and resolve any `AGDF_BACKLOG_STATUS_UNKNOWN` or `AGDF_BACKLOG_ARTEFACT_LABEL_UNKNOWN` finding before closeout. See the AGDF control scaffold's `MASTER_BACKLOG.md` template Rules section for the canonical status/artefact label vocabulary.
55
+ 16. After writing or updating `MASTER_BACKLOG.md`, run `doctor --json` (or the locally available equivalent) and resolve any `AGDF_BACKLOG_STATUS_UNKNOWN` or `AGDF_BACKLOG_ARTEFACT_LABEL_UNKNOWN` finding before closeout. See the AGDF control scaffold's `MASTER_BACKLOG.md` template Rules section for the canonical status/artefact label vocabulary.
56
56
 
57
57
  ## When To Use
58
58
  - at the end of every relevant run
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agdf",
3
- "version": "0.10.2",
3
+ "version": "0.10.3",
4
4
  "description": "AI Governance & Delivery Framework (AGDF) brings an operating system for governed AI-assisted delivery to Claude Code, combining workflow skills, control templates and machine-readable repository checks for gate decisions, Brownfield analysis, task-plan review, QA decisions, durable run state and auditable closeout.",
5
5
  "keywords": [
6
6
  "agentic",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agdf",
3
- "version": "0.10.2",
3
+ "version": "0.10.3",
4
4
  "description": "Operating system for governed AI-assisted delivery.",
5
5
  "author": {
6
6
  "name": "Arndt Gold",
@@ -11,6 +11,9 @@
11
11
  "en": {
12
12
  "statusCard": {
13
13
  "title": "AGDF status-card",
14
+ "status": "Status",
15
+ "field": "Status field",
16
+ "value": "Value",
14
17
  "run": "Selected run",
15
18
  "humanTitle": "Run title",
16
19
  "gate": "Current gate",
@@ -107,6 +110,7 @@
107
110
  "exactTextRequest": "To approve, reply exactly with `{approval}`; otherwise request revision or decline.",
108
111
  "decisionFollows": "will be the next user decision",
109
112
  "presentationFailure": "Approval cards could not be rendered safely.",
113
+ "statusPresentationFailure": "Status card could not render safely.",
110
114
  "nonReadyDecision": "No decision requested: {reason}.",
111
115
  "approveDescription": "Approve this gate with the exact authorization value.",
112
116
  "reviseLabel": "Revise",
@@ -214,6 +218,9 @@
214
218
  "de": {
215
219
  "statusCard": {
216
220
  "title": "AGDF-Statuskarte",
221
+ "status": "Status",
222
+ "field": "Statusfeld",
223
+ "value": "Wert",
217
224
  "run": "Ausgewählter Run",
218
225
  "humanTitle": "Run-Titel",
219
226
  "gate": "Aktuelles Gate",
@@ -310,6 +317,7 @@
310
317
  "exactTextRequest": "Zur Freigabe exakt mit `{approval}` antworten; andernfalls Überarbeitung anfordern oder ablehnen.",
311
318
  "decisionFollows": "ist die nächste Nutzerentscheidung",
312
319
  "presentationFailure": "Die Freigabekarten konnten nicht sicher dargestellt werden.",
320
+ "statusPresentationFailure": "Statuskarte nicht sicher darstellbar.",
313
321
  "nonReadyDecision": "Keine Entscheidung angefordert: {reason}.",
314
322
  "approveDescription": "Dieses Gate mit dem exakten Freigabewert autorisieren.",
315
323
  "reviseLabel": "Überarbeiten",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "id": "agdf",
4
- "version": "0.10.2",
4
+ "version": "0.10.3",
5
5
  "displayName": "AI Governance & Delivery Framework",
6
6
  "developerName": "Arndt Gold",
7
7
  "description": "Operating system for governed AI-assisted delivery.",
@@ -40,6 +40,29 @@ It must not introduce a second gate model or override `gate-check`, `delivery-ma
40
40
 
41
41
  `next_gate_after_approval` and `allowed_after_approval` must be `none` when no approval is missing, when the current step is internal, or when the run is in OR/completed handoff. They must not imply implementation, QA, release, commit, push or PR authority unless the existing gate model already allows that authority.
42
42
 
43
+ ### Deterministic Operational Presentation
44
+
45
+ `status_card` is the canonical machine/audit projection. The same evaluation also exposes an additive
46
+ `status_presentation` object with selected run/revision/gate/locale identity, `semantic_block:
47
+ run_status_card`, one compact Markdown block with localized labels and canonical evaluated values,
48
+ and `authorizes: false`. The full raw and evidence-bearing projection remains available unchanged in
49
+ `status_card` JSON for audit and automation; it is not duplicated into the primary chat card.
50
+ Fresh legacy-compatible control state without a durable revision uses the explicit non-authorizing
51
+ identity `unversioned`; ready approval presentation remains impossible without a real revision.
52
+
53
+ `interaction-presentation.js` is the only owner that may select, label, order, localize and render the
54
+ operational status fields. The gate-check CLI, skills and surface adapters consume
55
+ `status_presentation.markdown` verbatim. They may add concise surrounding explanation, but they must
56
+ not reconstruct a table, omit or rename fields, substitute generic gate actions for evaluated
57
+ `allowed_now`/`forbidden_now`, or maintain another status-card template. If the projection is missing
58
+ or its selected run, revision, gate or locale identity is stale, presentation fails closed rather
59
+ than falling back to model-generated status Markdown.
60
+
61
+ This determinism protects semantic parity, permission boundaries and evidence visibility across
62
+ hosts. It does not standardize host chrome or unrelated conversational prose. `approval_presentation`
63
+ remains the separate decision-time projection and is present only for a ready user gate; both public
64
+ presentations derive from the same `status_card` snapshot and neither grants approval.
65
+
43
66
 
44
67
  ### Breadcrumb
45
68
 
@@ -11,6 +11,9 @@
11
11
  "en": {
12
12
  "statusCard": {
13
13
  "title": "AGDF status-card",
14
+ "status": "Status",
15
+ "field": "Status field",
16
+ "value": "Value",
14
17
  "run": "Selected run",
15
18
  "humanTitle": "Run title",
16
19
  "gate": "Current gate",
@@ -107,6 +110,7 @@
107
110
  "exactTextRequest": "To approve, reply exactly with `{approval}`; otherwise request revision or decline.",
108
111
  "decisionFollows": "will be the next user decision",
109
112
  "presentationFailure": "Approval cards could not be rendered safely.",
113
+ "statusPresentationFailure": "Status card could not render safely.",
110
114
  "nonReadyDecision": "No decision requested: {reason}.",
111
115
  "approveDescription": "Approve this gate with the exact authorization value.",
112
116
  "reviseLabel": "Revise",
@@ -214,6 +218,9 @@
214
218
  "de": {
215
219
  "statusCard": {
216
220
  "title": "AGDF-Statuskarte",
221
+ "status": "Status",
222
+ "field": "Statusfeld",
223
+ "value": "Wert",
217
224
  "run": "Ausgewählter Run",
218
225
  "humanTitle": "Run-Titel",
219
226
  "gate": "Aktuelles Gate",
@@ -310,6 +317,7 @@
310
317
  "exactTextRequest": "Zur Freigabe exakt mit `{approval}` antworten; andernfalls Überarbeitung anfordern oder ablehnen.",
311
318
  "decisionFollows": "ist die nächste Nutzerentscheidung",
312
319
  "presentationFailure": "Die Freigabekarten konnten nicht sicher dargestellt werden.",
320
+ "statusPresentationFailure": "Statuskarte nicht sicher darstellbar.",
313
321
  "nonReadyDecision": "Keine Entscheidung angefordert: {reason}.",
314
322
  "approveDescription": "Dieses Gate mit dem exakten Freigabewert autorisieren.",
315
323
  "reviseLabel": "Überarbeiten",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "id": "agdf",
4
- "version": "0.10.2",
4
+ "version": "0.10.3",
5
5
  "displayName": "AI Governance & Delivery Framework",
6
6
  "developerName": "Arndt Gold",
7
7
  "description": "Operating system for governed AI-assisted delivery.",
@@ -24,7 +24,8 @@ export function createValidationHandlers(io = console) {
24
24
  const output = printApprovalEnvelope(report, { io, reEvaluate: () => evaluateGateCheck(options.dir, options) });
25
25
  return output.status === "blocked" ? 2 : 0;
26
26
  }
27
- printGateCheckReport(report, options.json, options.statusCard, io);
27
+ const presentationRendered = printGateCheckReport(report, options.json, options.statusCard, io);
28
+ if (presentationRendered === false) return 2;
28
29
  return report.status === "blocked" ? 2 : 0;
29
30
  }],
30
31
  ["delivery-map", (options) => {
@@ -1,4 +1,4 @@
1
- import { attachApprovalOrientationSnapshot, buildArtefactRefs, buildQualityReadiness, formatArtefactRefs, gateTitle, localePack, renderApprovalOrientationSnapshot, resolveHumanRunTitle } from '../interaction-presentation.js';
1
+ import { attachApprovalOrientationSnapshot, buildArtefactRefs, buildQualityReadiness, gateTitle, localePack, renderApprovalOrientationSnapshot, renderOperationalStatusCard, resolveHumanRunTitle } from '../interaction-presentation.js';
2
2
  import { interactionLocales, resolveConfiguredChatLanguage } from '../cli/runtime-context.js';
3
3
  import { evaluateDoctor } from './doctor.js';
4
4
  import { analyzeDeliveryMap, deriveQualityOutlook } from './delivery-map.js';
@@ -292,6 +292,11 @@ export function evaluateGateCheck(targetDir, selection = {}) {
292
292
  enumerable: false,
293
293
  });
294
294
  const revisionId = extractField(runState.content ?? "", "revision_id");
295
+ const statusPresentation = renderOperationalStatusCard(statusCard, {
296
+ registry: interactionLocales,
297
+ humanPresentation,
298
+ revisionId,
299
+ });
295
300
  const approvalOrientation = attachApprovalOrientationSnapshot(statusCard, {
296
301
  ready: isReadyUserGateApproval({ status, currentGate, missingApproval }),
297
302
  humanPresentation,
@@ -323,6 +328,7 @@ export function evaluateGateCheck(targetDir, selection = {}) {
323
328
  native_attempt_required: statusCard.native_attempt_required,
324
329
  candidate_runs: runState.candidate_runs ?? [],
325
330
  status_card: statusCard,
331
+ status_presentation: statusPresentation,
326
332
  approval_presentation: renderApprovalOrientationSnapshot(approvalOrientation, {
327
333
  registry: interactionLocales,
328
334
  expectedIdentity: {
@@ -392,28 +398,12 @@ export function printApprovalEnvelope(report, { io = console, reEvaluate } = {})
392
398
 
393
399
  function printGateCheckStatusCard(report, io) {
394
400
  const card = report.status_card;
395
- const labels = localePack(interactionLocales, card.presentation_language).statusCard;
396
- const primary = localePack(interactionLocales, card.presentation_language).primary;
397
- const presentation = card.humanPresentation ?? {
398
- runTitle: card.run_id,
399
- gateTitle: gateTitle(interactionLocales, card.presentation_language, card.current_gate),
400
- artefactRefs: buildArtefactRefs({}, interactionLocales, card.presentation_language),
401
- };
402
- io.log(`${labels.title}: ${primary.status[card.status] ?? card.status}`);
403
- io.log(`${labels.run}: ${card.run_id}`);
404
- io.log(`${labels.humanTitle}: ${presentation.runTitle}`);
405
- io.log(`${labels.gate}: ${card.current_gate} — ${presentation.gateTitle}`);
406
- io.log(`${labels.lifecycle}: ${primary.lifecycle[card.delivery_state] ?? primary.lifecycle.unknown}`);
407
- io.log(`${labels.artefacts}: ${formatArtefactRefs(presentation.artefactRefs)}`);
408
- io.log(`${labels.blocked}: ${card.blocking_condition === "none" ? primary.none : primary.blocked}`);
409
- io.log(`${labels.missing}: ${card.missing_approval === "none" ? primary.none : card.missing_approval}`);
410
- if (card.next_gate_after_approval !== "none") {
411
- io.log(`${labels.nextGate}: ${card.next_gate_after_approval} — ${gateTitle(interactionLocales, card.presentation_language, card.next_gate_after_approval)}`);
412
- io.log(`${labels.allowedAfter}: ${primary.afterApproval[card.current_gate] ?? primary.actions[card.current_gate]}`);
401
+ if (!report.status_presentation?.markdown) {
402
+ io.log(localePack(interactionLocales, card?.presentation_language || "en").interaction.statusPresentationFailure);
403
+ return false;
413
404
  }
414
- io.log(`${labels.allowed}: ${primary.actions[card.current_gate] ?? primary.none}`);
415
- io.log(`${labels.step}: ${primary.actions[card.current_gate] ?? primary.none}`);
416
- io.log(`${labels.quality}: ${primary.quality}`);
405
+ io.log(report.status_presentation.markdown);
406
+ const primary = localePack(interactionLocales, card.presentation_language).primary;
417
407
  const readiness = qualityReadinessForRunState(card.runState, report.next_allowed_action);
418
408
  if (readiness) {
419
409
  const quality = localePack(interactionLocales, card.presentation_language).qualityReadiness;
@@ -429,17 +419,17 @@ function printGateCheckStatusCard(report, io) {
429
419
  io.log(`${quality.nextAction}: ${readiness.next_action || primary.none}`);
430
420
  io.log(`${quality.decisionOwner}: ${quality.decisionOwnerValue}`);
431
421
  }
422
+ return true;
432
423
  }
433
424
 
434
425
  export function printGateCheckReport(report, json, statusCard = false, io = console) {
435
426
  if (json) {
436
427
  io.log(JSON.stringify(report, null, 2));
437
- return;
428
+ return true;
438
429
  }
439
430
 
440
431
  if (statusCard) {
441
- printGateCheckStatusCard(report, io);
442
- return;
432
+ return printGateCheckStatusCard(report, io);
443
433
  }
444
434
 
445
435
  io.log(`AGDF gate-check: ${report.status}`);
@@ -456,4 +446,5 @@ export function printGateCheckReport(report, json, statusCard = false, io = cons
456
446
  for (const item of report.forbidden) io.log(`- ${item}`);
457
447
  io.log("");
458
448
  io.log(`Next allowed action: ${report.next_allowed_action}`);
449
+ return true;
459
450
  }
@@ -269,6 +269,86 @@ export function formatArtefactRefs(refs) {
269
269
  return refs.map((ref) => ref.exists ? `[${ref.label}](${ref.path})` : ref.missingLabel).join(" · ");
270
270
  }
271
271
 
272
+ function markdownCell(value) {
273
+ return String(value ?? "")
274
+ .replaceAll("\\", "\\\\")
275
+ .replaceAll("|", "\\|")
276
+ .replaceAll("&", "&amp;")
277
+ .replaceAll("<", "&lt;")
278
+ .replaceAll(">", "&gt;")
279
+ .replace(/\r?\n/g, "<br>");
280
+ }
281
+
282
+ function operationalList(value, none) {
283
+ if (!Array.isArray(value) || value.length === 0) return none;
284
+ return value.map((item) => String(item ?? "").trim()).filter(Boolean).join("\n") || none;
285
+ }
286
+
287
+ function operationalBreadcrumb(value, labels) {
288
+ if (!Array.isArray(value) || value.length === 0) return "";
289
+ const marker = {
290
+ fulfilled: labels.breadcrumbFulfilled,
291
+ current: labels.breadcrumbCurrent,
292
+ open: labels.breadcrumbOpen,
293
+ };
294
+ return value.map((item) => `${marker[item?.status] ?? labels.breadcrumbOpen} ${String(item?.gate ?? "").trim()}`)
295
+ .filter((item) => item.trim())
296
+ .join(labels.breadcrumbSeparator);
297
+ }
298
+
299
+ export function renderOperationalStatusCard(statusCard, {
300
+ registry,
301
+ humanPresentation,
302
+ revisionId = "",
303
+ } = {}) {
304
+ if (!plainObject(statusCard) || !plainObject(humanPresentation)) return null;
305
+ const runId = String(statusCard.run_id ?? "").trim();
306
+ const currentGate = String(statusCard.current_gate ?? "").trim();
307
+ const revision = String(revisionId ?? "").trim() || "unversioned";
308
+ const locale = resolvePresentationLocale(registry, statusCard.presentation_language);
309
+ if (!runId || !currentGate || !locale) return null;
310
+
311
+ const pack = localePack(registry, locale);
312
+ const labels = pack.statusCard;
313
+ const primary = pack.primary;
314
+ const none = primary.none;
315
+ const gateLabel = String(humanPresentation.gateTitle ?? "").trim() || gateTitle(registry, locale, currentGate);
316
+ const runTitle = displaySafeTitle(humanPresentation.runTitle, normalizedRunTitle(runId));
317
+ const breadcrumb = operationalBreadcrumb(statusCard.breadcrumb, labels);
318
+ const rows = [
319
+ [labels.run, `${runTitle} · \`${runId}\``],
320
+ ...(breadcrumb ? [[labels.breadcrumb, breadcrumb]] : []),
321
+ [labels.status, primary.status[statusCard.status] ?? statusCard.status],
322
+ [labels.gate, `${gateLabel} (\`${currentGate}\`)`],
323
+ [labels.allowed, operationalList(statusCard.allowed_now, none)],
324
+ [labels.forbidden, operationalList(statusCard.forbidden_now, none)],
325
+ [labels.blocked, statusCard.blocking_condition === "none" ? none : statusCard.blocking_condition],
326
+ [labels.missing, statusCard.missing_approval === "none" ? none : statusCard.missing_approval],
327
+ [labels.nextGate, statusCard.next_gate_after_approval === "none" ? none : statusCard.next_gate_after_approval],
328
+ [labels.allowedAfter, statusCard.allowed_after_approval === "none" ? none : statusCard.allowed_after_approval],
329
+ [labels.step, statusCard.next_step || none],
330
+ [labels.quality, statusCard.quality_outlook || none],
331
+ ];
332
+ const markdown = [
333
+ `## ${labels.title}`,
334
+ "",
335
+ `| ${labels.field} | ${labels.value} |`,
336
+ "|---|---|",
337
+ ...rows.map(([label, value]) => `| ${markdownCell(label)} | ${markdownCell(value)} |`),
338
+ ].join("\n");
339
+
340
+ return Object.freeze({
341
+ schema_version: "1",
342
+ semantic_block: "run_status_card",
343
+ run_id: runId,
344
+ revision_id: revision,
345
+ current_gate: currentGate,
346
+ presentation_language: locale,
347
+ markdown,
348
+ authorizes: false,
349
+ });
350
+ }
351
+
272
352
  export function gateOptions(registry, requestedLocale, gate, { includeCancel = false } = {}) {
273
353
  const pack = localePack(registry, requestedLocale);
274
354
  const options = [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-agdf",
3
- "version": "0.10.2",
3
+ "version": "0.10.3",
4
4
  "description": "Bootstrap AGDF repository-local plugin files, Copilot/OpenCode instructions, skills, control files and machine-readable checks.",
5
5
  "type": "module",
6
6
  "files": [
@@ -2,7 +2,7 @@
2
2
  "schema_version": "1",
3
3
  "generated": true,
4
4
  "source": "create-agdf/cli",
5
- "version": "0.10.2",
5
+ "version": "0.10.3",
6
6
  "entrypoint": "create-agdf/bin/agdf-validator.js",
7
- "digest": "9103a8c171c070432f22b4f3c40e39c46ddd183f8fabfe7fb701784aab8ca2d3"
7
+ "digest": "f95766cb6826e5e2a27c39ac6ddfc720ed8d3388e07f40411cffcccca925a7ed"
8
8
  }
@@ -369,6 +369,16 @@ for (const required of gateCheckOperationalBoundaries) {
369
369
  for (const duplicatedPolicy of ["Surface behavior:", "Make exactly one native-attempt", "Interaction Locale Contract", "decorated_label_only"]) {
370
370
  if (gateCheckSkill.includes(duplicatedPolicy)) failures.push(`gate-check must not duplicate normative interaction policy: ${duplicatedPolicy}`);
371
371
  }
372
+ if (!gateCheckSkill.includes("`status_presentation.markdown` verbatim")) {
373
+ failures.push("gate-check must consume the deterministic operational status presentation");
374
+ }
375
+ if (gateCheckSkill.includes("| Run status | Value |")) {
376
+ failures.push("gate-check must not maintain a second operational status-card template");
377
+ }
378
+ if (!runtimeContract.includes("### Deterministic Operational Presentation")
379
+ || !/must\s+not reconstruct a table/.test(runtimeContract)) {
380
+ failures.push("interaction contract must own deterministic operational status presentation");
381
+ }
372
382
 
373
383
  for (const gate of ["UR", "PRD", "SD", "TP", "QA", "UAT"]) {
374
384
  for (const locale of ["en", "de"]) {
@@ -824,11 +834,8 @@ for (const skill of expectedSkills) {
824
834
  if (!skillMd.includes("The canonical gate order and transition model live only in `../../meta/contracts/gate-transition.md`")) {
825
835
  failures.push("gate-check must point to gate-transition.md as gate transition SoT");
826
836
  }
827
- for (const label of ["Status", "Current gate", "Allowed now", "Blocked by", "Missing approval", "Next step", "Quality outlook"]) {
828
- if (!skillMd.includes(`| ${label} |`)) failures.push(`gate-check must render Run Status Card label: ${label}`);
829
- }
830
- if (!skillMd.includes("Next gate after approval") || !skillMd.includes("Allowed after approval")) {
831
- failures.push("gate-check must render post-approval Run Status Card fields when an approval is missing");
837
+ if (!skillMd.includes("`status_presentation.markdown` verbatim") || !skillMd.includes("Do not maintain or render a skill-local table template")) {
838
+ failures.push("gate-check must delegate operational status rendering to the canonical projection");
832
839
  }
833
840
  if (!skillMd.includes("A decision value without scope reason and evidence is still missing")) {
834
841
  failures.push("gate-check must require evidenced Mode/Slice Decision before later work");
@@ -864,6 +871,14 @@ for (const skill of expectedSkills) {
864
871
  failures.push("gate-check must classify CLI reports as validators, not the primary workflow");
865
872
  }
866
873
  }
874
+ if (skill === "release-or") {
875
+ const rulesSection = skillMd.match(/## Rules\r?\n([\s\S]*?)(?=\r?\n## )/)?.[1] ?? "";
876
+ const ruleNumbers = [...rulesSection.matchAll(/^(\d+)\. /gm)].map((match) => Number(match[1]));
877
+ const expectedRuleNumbers = ruleNumbers.map((_, index) => index + 1);
878
+ if (ruleNumbers.length === 0 || JSON.stringify(ruleNumbers) !== JSON.stringify(expectedRuleNumbers)) {
879
+ failures.push(`release-or Rules numbering must be sequential from 1, got ${ruleNumbers.join(", ") || "none"}`);
880
+ }
881
+ }
867
882
 
868
883
  for (const [pathLabel, content] of [
869
884
  [`${skill}/SKILL.md`, skillMd],
@@ -85,10 +85,11 @@ This skill owns only these operational responsibilities:
85
85
  5. Revalidate the same run, gate and revision immediately after the response and before persistence.
86
86
  6. Persist only a currently valid exact approval through the existing control-state workflow.
87
87
 
88
- For status, blocked, read-only or rationale interactions, consume the same canonical projection and
89
- locale contract without asking for approval. Internal process steps remain distinct from user
90
- decisions. If the projection, adapter evidence or revalidation is missing, follow the contract's
91
- fail-closed outcome and leave authority unchanged.
88
+ For status, blocked, read-only or rationale interactions, consume the canonical
89
+ `status_presentation.markdown` verbatim and apply the locale contract without asking for approval.
90
+ Do not reconstruct its fields, ordering, labels or Markdown from `status_card` JSON. Internal process
91
+ steps remain distinct from user decisions. If the projection, adapter evidence or revalidation is
92
+ missing, follow the contract's fail-closed outcome and leave authority unchanged.
92
93
 
93
94
  ## Rules
94
95
  1. Fail closed when a required approval or artefact status is missing.
@@ -163,23 +164,15 @@ If a status is not explicit, do not assume it is satisfied.
163
164
  12. If branch, workspace and durable artefacts disagree, do not choose a scope silently; report the ambiguity and ask for the smallest clarifying gate action.
164
165
 
165
166
  ## Output
166
- Keep the result short and operational. Render the Runtime Contract's compact human-facing Run Status Card rather than a surface-specific summary:
167
-
168
- | Run status | Value |
169
- |---|---|
170
- | Status | `open | blocked` |
171
- | Current gate | `<GateName or internal step>` |
172
- | Allowed now | `<currently allowed outputs>` |
173
- | Blocked by | `<blocking condition or none>` |
174
- | Missing approval | `Approval: <GateName> | none` |
175
- | Next step | `<single permissible next step>` |
176
- | Quality outlook | `<next meaningful quality focus or none>` |
177
-
178
- When an approval is missing, also include `Next gate after approval` and `Allowed after approval` exactly as constrained by the Runtime Contract. Keep forbidden outputs, evidence and next-skill detail in the concise surrounding text when relevant; they are not extra status-card rows.
167
+ Keep the result short and operational. For a status-only response, consume the Runtime Contract's
168
+ code-owned `status_presentation.markdown` verbatim. It is the compact localized operational Run
169
+ Status Card and already includes current and post-approval authority, next-step and quality fields.
170
+ Keep full raw evidence in `status_card` JSON or concise surrounding detail when relevant; do not turn
171
+ the primary chat card into an audit dump. Do not maintain or render a skill-local table template.
179
172
 
180
173
  This complete operational status table remains the status-reporting and detail
181
174
  surface. When the same response immediately requests a ready gate approval,
182
- render its six-field compact approval-time projection first, then the Gate
175
+ render its five-field compact approval-time projection first, then the Gate
183
176
  Transition Card in the same immediately preceding assistant message, then
184
177
  invoke exactly one native question or exact-text fallback. Both cards derive
185
178
  from the same snapshot and are shown exactly once.
@@ -52,7 +52,7 @@ Use the Runtime Contract definition of `Relevant Run` to decide whether OR is ma
52
52
  13. Keep machine status normalization in the CLI; do not write internal snake_case status values into the human-facing backlog.
53
53
  14. Reconcile Context Graph impact before clean closeout: report `context_graph_reconciliation: resolved | not_applicable | open_gap`.
54
54
  15. If Context Graph work remains unresolved, report it as an explicit open gap and do not describe the run as cleanly handoff-ready.
55
- 15. After writing or updating `MASTER_BACKLOG.md`, run `doctor --json` (or the locally available equivalent) and resolve any `AGDF_BACKLOG_STATUS_UNKNOWN` or `AGDF_BACKLOG_ARTEFACT_LABEL_UNKNOWN` finding before closeout. See the AGDF control scaffold's `MASTER_BACKLOG.md` template Rules section for the canonical status/artefact label vocabulary.
55
+ 16. After writing or updating `MASTER_BACKLOG.md`, run `doctor --json` (or the locally available equivalent) and resolve any `AGDF_BACKLOG_STATUS_UNKNOWN` or `AGDF_BACKLOG_ARTEFACT_LABEL_UNKNOWN` finding before closeout. See the AGDF control scaffold's `MASTER_BACKLOG.md` template Rules section for the canonical status/artefact label vocabulary.
56
56
 
57
57
  ## When To Use
58
58
  - at the end of every relevant run
@@ -24,7 +24,8 @@ export function createValidationHandlers(io = console) {
24
24
  const output = printApprovalEnvelope(report, { io, reEvaluate: () => evaluateGateCheck(options.dir, options) });
25
25
  return output.status === "blocked" ? 2 : 0;
26
26
  }
27
- printGateCheckReport(report, options.json, options.statusCard, io);
27
+ const presentationRendered = printGateCheckReport(report, options.json, options.statusCard, io);
28
+ if (presentationRendered === false) return 2;
28
29
  return report.status === "blocked" ? 2 : 0;
29
30
  }],
30
31
  ["delivery-map", (options) => {
@@ -1,4 +1,4 @@
1
- import { attachApprovalOrientationSnapshot, buildArtefactRefs, buildQualityReadiness, formatArtefactRefs, gateTitle, localePack, renderApprovalOrientationSnapshot, resolveHumanRunTitle } from '../interaction-presentation.js';
1
+ import { attachApprovalOrientationSnapshot, buildArtefactRefs, buildQualityReadiness, gateTitle, localePack, renderApprovalOrientationSnapshot, renderOperationalStatusCard, resolveHumanRunTitle } from '../interaction-presentation.js';
2
2
  import { interactionLocales, resolveConfiguredChatLanguage } from '../cli/runtime-context.js';
3
3
  import { evaluateDoctor } from './doctor.js';
4
4
  import { analyzeDeliveryMap, deriveQualityOutlook } from './delivery-map.js';
@@ -292,6 +292,11 @@ export function evaluateGateCheck(targetDir, selection = {}) {
292
292
  enumerable: false,
293
293
  });
294
294
  const revisionId = extractField(runState.content ?? "", "revision_id");
295
+ const statusPresentation = renderOperationalStatusCard(statusCard, {
296
+ registry: interactionLocales,
297
+ humanPresentation,
298
+ revisionId,
299
+ });
295
300
  const approvalOrientation = attachApprovalOrientationSnapshot(statusCard, {
296
301
  ready: isReadyUserGateApproval({ status, currentGate, missingApproval }),
297
302
  humanPresentation,
@@ -323,6 +328,7 @@ export function evaluateGateCheck(targetDir, selection = {}) {
323
328
  native_attempt_required: statusCard.native_attempt_required,
324
329
  candidate_runs: runState.candidate_runs ?? [],
325
330
  status_card: statusCard,
331
+ status_presentation: statusPresentation,
326
332
  approval_presentation: renderApprovalOrientationSnapshot(approvalOrientation, {
327
333
  registry: interactionLocales,
328
334
  expectedIdentity: {
@@ -392,28 +398,12 @@ export function printApprovalEnvelope(report, { io = console, reEvaluate } = {})
392
398
 
393
399
  function printGateCheckStatusCard(report, io) {
394
400
  const card = report.status_card;
395
- const labels = localePack(interactionLocales, card.presentation_language).statusCard;
396
- const primary = localePack(interactionLocales, card.presentation_language).primary;
397
- const presentation = card.humanPresentation ?? {
398
- runTitle: card.run_id,
399
- gateTitle: gateTitle(interactionLocales, card.presentation_language, card.current_gate),
400
- artefactRefs: buildArtefactRefs({}, interactionLocales, card.presentation_language),
401
- };
402
- io.log(`${labels.title}: ${primary.status[card.status] ?? card.status}`);
403
- io.log(`${labels.run}: ${card.run_id}`);
404
- io.log(`${labels.humanTitle}: ${presentation.runTitle}`);
405
- io.log(`${labels.gate}: ${card.current_gate} — ${presentation.gateTitle}`);
406
- io.log(`${labels.lifecycle}: ${primary.lifecycle[card.delivery_state] ?? primary.lifecycle.unknown}`);
407
- io.log(`${labels.artefacts}: ${formatArtefactRefs(presentation.artefactRefs)}`);
408
- io.log(`${labels.blocked}: ${card.blocking_condition === "none" ? primary.none : primary.blocked}`);
409
- io.log(`${labels.missing}: ${card.missing_approval === "none" ? primary.none : card.missing_approval}`);
410
- if (card.next_gate_after_approval !== "none") {
411
- io.log(`${labels.nextGate}: ${card.next_gate_after_approval} — ${gateTitle(interactionLocales, card.presentation_language, card.next_gate_after_approval)}`);
412
- io.log(`${labels.allowedAfter}: ${primary.afterApproval[card.current_gate] ?? primary.actions[card.current_gate]}`);
401
+ if (!report.status_presentation?.markdown) {
402
+ io.log(localePack(interactionLocales, card?.presentation_language || "en").interaction.statusPresentationFailure);
403
+ return false;
413
404
  }
414
- io.log(`${labels.allowed}: ${primary.actions[card.current_gate] ?? primary.none}`);
415
- io.log(`${labels.step}: ${primary.actions[card.current_gate] ?? primary.none}`);
416
- io.log(`${labels.quality}: ${primary.quality}`);
405
+ io.log(report.status_presentation.markdown);
406
+ const primary = localePack(interactionLocales, card.presentation_language).primary;
417
407
  const readiness = qualityReadinessForRunState(card.runState, report.next_allowed_action);
418
408
  if (readiness) {
419
409
  const quality = localePack(interactionLocales, card.presentation_language).qualityReadiness;
@@ -429,17 +419,17 @@ function printGateCheckStatusCard(report, io) {
429
419
  io.log(`${quality.nextAction}: ${readiness.next_action || primary.none}`);
430
420
  io.log(`${quality.decisionOwner}: ${quality.decisionOwnerValue}`);
431
421
  }
422
+ return true;
432
423
  }
433
424
 
434
425
  export function printGateCheckReport(report, json, statusCard = false, io = console) {
435
426
  if (json) {
436
427
  io.log(JSON.stringify(report, null, 2));
437
- return;
428
+ return true;
438
429
  }
439
430
 
440
431
  if (statusCard) {
441
- printGateCheckStatusCard(report, io);
442
- return;
432
+ return printGateCheckStatusCard(report, io);
443
433
  }
444
434
 
445
435
  io.log(`AGDF gate-check: ${report.status}`);
@@ -456,4 +446,5 @@ export function printGateCheckReport(report, json, statusCard = false, io = cons
456
446
  for (const item of report.forbidden) io.log(`- ${item}`);
457
447
  io.log("");
458
448
  io.log(`Next allowed action: ${report.next_allowed_action}`);
449
+ return true;
459
450
  }
@@ -14,6 +14,7 @@ const globalOpenCodeSkillOwnershipMarker = "<!-- AGDF-GLOBAL-SKILL: ";
14
14
  const globalOpenCodeInstructionsOwnershipMarker = "<!-- AGDF-GLOBAL-INSTRUCTIONS -->";
15
15
  const globalOpenCodeRuntimeContractOwnershipMarker = "<!-- AGDF-GLOBAL-RUNTIME-CONTRACT -->";
16
16
  const globalOpenCodeValidatorOwnershipMarker = "// AGDF-GLOBAL-LOCAL-VALIDATOR";
17
+ const globalOpenCodeValidatorPackageOwner = "create-agdf";
17
18
  const testNpmCliPath = process.env.NODE_ENV === "test" ? process.env.AGDF_TEST_NPM_CLI_PATH || "" : "";
18
19
  const npmCommand = testNpmCliPath ? process.execPath : process.platform === "win32" ? process.execPath : "npm";
19
20
  const npmPrefixArgs = testNpmCliPath ? [testNpmCliPath] : process.platform === "win32" ? [join(dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js")] : [];
@@ -74,16 +75,23 @@ export function installOpenCodeGlobalPlugin(configDir) {
74
75
  nextConfig.instructions = [...(nextConfig.instructions ?? [])];
75
76
  if (!nextConfig.instructions.includes("AGDF.md")) nextConfig.instructions.push("AGDF.md");
76
77
 
78
+ const canonicalPermissions = pluginDefinition.opencode.permissions;
77
79
  if (nextConfig.permission === undefined) {
78
- nextConfig.permission = { question: "allow", skill: { "agdf-*": "allow" } };
80
+ nextConfig.permission = JSON.parse(JSON.stringify(canonicalPermissions));
79
81
  } else if (nextConfig.permission && typeof nextConfig.permission === "object" && !Array.isArray(nextConfig.permission)) {
80
82
  nextConfig.permission = { ...nextConfig.permission };
81
- if (nextConfig.permission.question === undefined) nextConfig.permission.question = "allow";
83
+ for (const permissionName of ["question", "edit", "bash"]) {
84
+ if (nextConfig.permission[permissionName] === undefined) {
85
+ nextConfig.permission[permissionName] = canonicalPermissions[permissionName];
86
+ }
87
+ }
82
88
  if (nextConfig.permission.skill === undefined) {
83
- nextConfig.permission.skill = { "agdf-*": "allow" };
89
+ nextConfig.permission.skill = { ...canonicalPermissions.skill };
84
90
  } else if (nextConfig.permission.skill && typeof nextConfig.permission.skill === "object" && !Array.isArray(nextConfig.permission.skill)) {
85
91
  nextConfig.permission.skill = { ...nextConfig.permission.skill };
86
- if (nextConfig.permission.skill["agdf-*"] === undefined) nextConfig.permission.skill["agdf-*"] = "allow";
92
+ for (const [skillPattern, decision] of Object.entries(canonicalPermissions.skill)) {
93
+ if (nextConfig.permission.skill[skillPattern] === undefined) nextConfig.permission.skill[skillPattern] = decision;
94
+ }
87
95
  }
88
96
  }
89
97
 
@@ -122,9 +130,35 @@ function globalOpenCodeConfigPaths(configDir) {
122
130
  contracts: join(configDir, "contracts"),
123
131
  skills: join(configDir, "skills"),
124
132
  localValidator: join(configDir, "agdf", "bin", "agdf-local.js"),
133
+ localValidatorPackage: join(configDir, "agdf", "package.json"),
125
134
  };
126
135
  }
127
136
 
137
+ function readGlobalOpenCodeValidatorPackage(path) {
138
+ if (!existsSync(path)) return null;
139
+ let existing;
140
+ try {
141
+ existing = JSON.parse(readFileSync(path, "utf8"));
142
+ } catch {
143
+ return null;
144
+ }
145
+ return existing;
146
+ }
147
+
148
+ function globalOpenCodeValidatorPackageIsValid(path) {
149
+ const manifest = readGlobalOpenCodeValidatorPackage(path);
150
+ return manifest?.type === "module"
151
+ && manifest?.agdf?.owner === globalOpenCodeValidatorPackageOwner
152
+ && manifest?.agdf?.surface === "opencode-global-validator";
153
+ }
154
+
155
+ function assertGlobalOpenCodeValidatorPackageWritable(path) {
156
+ if (!existsSync(path)) return;
157
+ if (!globalOpenCodeValidatorPackageIsValid(path)) {
158
+ throw new Error(`Refusing to overwrite unowned global OpenCode validator package: ${path}`);
159
+ }
160
+ }
161
+
128
162
  function globalOpenCodeOwnershipMarkerIsValid(content, marker, placement) {
129
163
  const lines = content.split(/\r?\n/);
130
164
  if (placement === "first-line") return lines[0] === marker;
@@ -145,6 +179,7 @@ function assertGlobalOpenCodeSurfaceWritable(configDir) {
145
179
  assertGlobalOpenCodeFileWritable(paths.instructions, globalOpenCodeInstructionsOwnershipMarker, "first-line");
146
180
  assertGlobalOpenCodeFileWritable(paths.runtimeContract, globalOpenCodeRuntimeContractOwnershipMarker, "first-line");
147
181
  assertGlobalOpenCodeFileWritable(paths.localValidator, globalOpenCodeValidatorOwnershipMarker, "first-line");
182
+ assertGlobalOpenCodeValidatorPackageWritable(paths.localValidatorPackage);
148
183
  for (const moduleName of contractModules) {
149
184
  assertGlobalOpenCodeFileWritable(
150
185
  join(paths.contracts, moduleName),
@@ -202,6 +237,7 @@ function toGlobalOpenCodeContent(content) {
202
237
  }
203
238
 
204
239
  export function installOpenCodeGlobalSurface(configDir) {
240
+ assertGlobalOpenCodeSurfaceWritable(configDir);
205
241
  const paths = globalOpenCodeConfigPaths(configDir);
206
242
  const generatedOpenCodeRoot = join(generatedRoot, ".opencode");
207
243
  const generatedInstructions = readFileSync(join(generatedOpenCodeRoot, pluginDefinition.opencode.instructionsFileName), "utf8");
@@ -223,6 +259,16 @@ export function installOpenCodeGlobalSurface(configDir) {
223
259
  writeOwnedGlobalOpenCodeFile(paths.instructions, globalInstructions, globalOpenCodeInstructionsOwnershipMarker, "first-line");
224
260
  writeOwnedGlobalOpenCodeFile(paths.runtimeContract, globalRuntimeContract, globalOpenCodeRuntimeContractOwnershipMarker, "first-line");
225
261
  writeOwnedGlobalOpenCodeFile(paths.localValidator, localValidator, globalOpenCodeValidatorOwnershipMarker, "first-line");
262
+ mkdirSync(dirname(paths.localValidatorPackage), { recursive: true });
263
+ writeFileSync(paths.localValidatorPackage, `${JSON.stringify({
264
+ name: "agdf-opencode-validator-runtime",
265
+ private: true,
266
+ type: "module",
267
+ agdf: {
268
+ owner: globalOpenCodeValidatorPackageOwner,
269
+ surface: "opencode-global-validator",
270
+ },
271
+ }, null, 2)}\n`, "utf8");
226
272
  for (const moduleName of contractModules) {
227
273
  const generatedModule = readFileSync(join(generatedOpenCodeRoot, "contracts", moduleName), "utf8");
228
274
  writeOwnedGlobalOpenCodeFile(
@@ -249,6 +295,7 @@ export function installOpenCodeGlobalSurface(configDir) {
249
295
  contracts: paths.contracts,
250
296
  skills: paths.skills,
251
297
  localValidator: paths.localValidator,
298
+ localValidatorPackage: paths.localValidatorPackage,
252
299
  };
253
300
  }
254
301
 
@@ -352,6 +399,7 @@ function evaluateGlobalOpenCodeSurface(configDir) {
352
399
  && contractCount === contractModules.length
353
400
  && skillCount === openCodeSkillNames.length
354
401
  && existsSync(paths.localValidator)
402
+ && globalOpenCodeValidatorPackageIsValid(paths.localValidatorPackage)
355
403
  && validatorResolution.envelope.machine_validation === "owned_version_matched",
356
404
  };
357
405
  }
@@ -269,6 +269,86 @@ export function formatArtefactRefs(refs) {
269
269
  return refs.map((ref) => ref.exists ? `[${ref.label}](${ref.path})` : ref.missingLabel).join(" · ");
270
270
  }
271
271
 
272
+ function markdownCell(value) {
273
+ return String(value ?? "")
274
+ .replaceAll("\\", "\\\\")
275
+ .replaceAll("|", "\\|")
276
+ .replaceAll("&", "&amp;")
277
+ .replaceAll("<", "&lt;")
278
+ .replaceAll(">", "&gt;")
279
+ .replace(/\r?\n/g, "<br>");
280
+ }
281
+
282
+ function operationalList(value, none) {
283
+ if (!Array.isArray(value) || value.length === 0) return none;
284
+ return value.map((item) => String(item ?? "").trim()).filter(Boolean).join("\n") || none;
285
+ }
286
+
287
+ function operationalBreadcrumb(value, labels) {
288
+ if (!Array.isArray(value) || value.length === 0) return "";
289
+ const marker = {
290
+ fulfilled: labels.breadcrumbFulfilled,
291
+ current: labels.breadcrumbCurrent,
292
+ open: labels.breadcrumbOpen,
293
+ };
294
+ return value.map((item) => `${marker[item?.status] ?? labels.breadcrumbOpen} ${String(item?.gate ?? "").trim()}`)
295
+ .filter((item) => item.trim())
296
+ .join(labels.breadcrumbSeparator);
297
+ }
298
+
299
+ export function renderOperationalStatusCard(statusCard, {
300
+ registry,
301
+ humanPresentation,
302
+ revisionId = "",
303
+ } = {}) {
304
+ if (!plainObject(statusCard) || !plainObject(humanPresentation)) return null;
305
+ const runId = String(statusCard.run_id ?? "").trim();
306
+ const currentGate = String(statusCard.current_gate ?? "").trim();
307
+ const revision = String(revisionId ?? "").trim() || "unversioned";
308
+ const locale = resolvePresentationLocale(registry, statusCard.presentation_language);
309
+ if (!runId || !currentGate || !locale) return null;
310
+
311
+ const pack = localePack(registry, locale);
312
+ const labels = pack.statusCard;
313
+ const primary = pack.primary;
314
+ const none = primary.none;
315
+ const gateLabel = String(humanPresentation.gateTitle ?? "").trim() || gateTitle(registry, locale, currentGate);
316
+ const runTitle = displaySafeTitle(humanPresentation.runTitle, normalizedRunTitle(runId));
317
+ const breadcrumb = operationalBreadcrumb(statusCard.breadcrumb, labels);
318
+ const rows = [
319
+ [labels.run, `${runTitle} · \`${runId}\``],
320
+ ...(breadcrumb ? [[labels.breadcrumb, breadcrumb]] : []),
321
+ [labels.status, primary.status[statusCard.status] ?? statusCard.status],
322
+ [labels.gate, `${gateLabel} (\`${currentGate}\`)`],
323
+ [labels.allowed, operationalList(statusCard.allowed_now, none)],
324
+ [labels.forbidden, operationalList(statusCard.forbidden_now, none)],
325
+ [labels.blocked, statusCard.blocking_condition === "none" ? none : statusCard.blocking_condition],
326
+ [labels.missing, statusCard.missing_approval === "none" ? none : statusCard.missing_approval],
327
+ [labels.nextGate, statusCard.next_gate_after_approval === "none" ? none : statusCard.next_gate_after_approval],
328
+ [labels.allowedAfter, statusCard.allowed_after_approval === "none" ? none : statusCard.allowed_after_approval],
329
+ [labels.step, statusCard.next_step || none],
330
+ [labels.quality, statusCard.quality_outlook || none],
331
+ ];
332
+ const markdown = [
333
+ `## ${labels.title}`,
334
+ "",
335
+ `| ${labels.field} | ${labels.value} |`,
336
+ "|---|---|",
337
+ ...rows.map(([label, value]) => `| ${markdownCell(label)} | ${markdownCell(value)} |`),
338
+ ].join("\n");
339
+
340
+ return Object.freeze({
341
+ schema_version: "1",
342
+ semantic_block: "run_status_card",
343
+ run_id: runId,
344
+ revision_id: revision,
345
+ current_gate: currentGate,
346
+ presentation_language: locale,
347
+ markdown,
348
+ authorizes: false,
349
+ });
350
+ }
351
+
272
352
  export function gateOptions(registry, requestedLocale, gate, { includeCancel = false } = {}) {
273
353
  const pack = localePack(registry, requestedLocale);
274
354
  const options = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-agdf",
3
- "version": "0.10.2",
3
+ "version": "0.10.3",
4
4
  "description": "Bootstrap AGDF repository-local plugin files, Copilot/OpenCode instructions, skills, control files and machine-readable checks.",
5
5
  "type": "module",
6
6
  "files": [