@sun-asterisk/sungen 3.3.0-alpha.1 → 3.3.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capabilities/discover.d.ts +4 -0
- package/dist/capabilities/discover.d.ts.map +1 -1
- package/dist/capabilities/discover.js +16 -2
- package/dist/capabilities/discover.js.map +1 -1
- package/dist/cli/case-triage-hint.d.ts +14 -0
- package/dist/cli/case-triage-hint.d.ts.map +1 -0
- package/dist/cli/case-triage-hint.js +23 -0
- package/dist/cli/case-triage-hint.js.map +1 -0
- package/dist/cli/commands/audit.js +1 -1
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/contract-derive-command.d.ts +4 -2
- package/dist/cli/commands/contract-derive-command.d.ts.map +1 -1
- package/dist/cli/commands/contract-derive-command.js +19 -5
- package/dist/cli/commands/contract-derive-command.js.map +1 -1
- package/dist/cli/commands/contract.d.ts +3 -0
- package/dist/cli/commands/contract.d.ts.map +1 -1
- package/dist/cli/commands/contract.js +40 -7
- package/dist/cli/commands/contract.js.map +1 -1
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +59 -18
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/cli/commands/gate.d.ts.map +1 -1
- package/dist/cli/commands/gate.js +2 -1
- package/dist/cli/commands/gate.js.map +1 -1
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +14 -2
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/journey.d.ts.map +1 -1
- package/dist/cli/commands/journey.js +6 -4
- package/dist/cli/commands/journey.js.map +1 -1
- package/dist/cli/commands/next.d.ts +7 -2
- package/dist/cli/commands/next.d.ts.map +1 -1
- package/dist/cli/commands/next.js +41 -10
- package/dist/cli/commands/next.js.map +1 -1
- package/dist/cli/commands/triage.d.ts +2 -1
- package/dist/cli/commands/triage.d.ts.map +1 -1
- package/dist/cli/commands/triage.js +45 -3
- package/dist/cli/commands/triage.js.map +1 -1
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +6 -2
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/index.js +5 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/json-error-on-exit.d.ts +34 -0
- package/dist/cli/json-error-on-exit.d.ts.map +1 -0
- package/dist/cli/json-error-on-exit.js +63 -0
- package/dist/cli/json-error-on-exit.js.map +1 -0
- package/dist/cli/project-sungen-invocation.d.ts +13 -0
- package/dist/cli/project-sungen-invocation.d.ts.map +1 -0
- package/dist/cli/project-sungen-invocation.js +95 -0
- package/dist/cli/project-sungen-invocation.js.map +1 -0
- package/dist/cli/run-command-hints.d.ts +9 -0
- package/dist/cli/run-command-hints.d.ts.map +1 -0
- package/dist/cli/run-command-hints.js +84 -0
- package/dist/cli/run-command-hints.js.map +1 -0
- package/dist/cli/waiting-for-you-block.d.ts +18 -0
- package/dist/cli/waiting-for-you-block.d.ts.map +1 -0
- package/dist/cli/waiting-for-you-block.js +22 -0
- package/dist/cli/waiting-for-you-block.js.map +1 -0
- package/dist/contract/accept-opaque-steps.d.ts +3 -0
- package/dist/contract/accept-opaque-steps.d.ts.map +1 -0
- package/dist/contract/accept-opaque-steps.js +114 -0
- package/dist/contract/accept-opaque-steps.js.map +1 -0
- package/dist/contract/approve-contract.d.ts +8 -1
- package/dist/contract/approve-contract.d.ts.map +1 -1
- package/dist/contract/approve-contract.js +6 -0
- package/dist/contract/approve-contract.js.map +1 -1
- package/dist/contract/build-contract-verdict.js +2 -2
- package/dist/contract/build-contract-verdict.js.map +1 -1
- package/dist/contract/build-trace-conformance.d.ts +5 -1
- package/dist/contract/build-trace-conformance.d.ts.map +1 -1
- package/dist/contract/build-trace-conformance.js +12 -14
- package/dist/contract/build-trace-conformance.js.map +1 -1
- package/dist/contract/classify-contract-change.d.ts +3 -2
- package/dist/contract/classify-contract-change.d.ts.map +1 -1
- package/dist/contract/classify-contract-change.js.map +1 -1
- package/dist/contract/classify-trace-conformance.d.ts +18 -3
- package/dist/contract/classify-trace-conformance.d.ts.map +1 -1
- package/dist/contract/classify-trace-conformance.js +64 -17
- package/dist/contract/classify-trace-conformance.js.map +1 -1
- package/dist/contract/contract-schema.d.ts +21 -0
- package/dist/contract/contract-schema.d.ts.map +1 -1
- package/dist/contract/contract-schema.js +25 -3
- package/dist/contract/contract-schema.js.map +1 -1
- package/dist/contract/contracts-lock-file.d.ts.map +1 -1
- package/dist/contract/contracts-lock-file.js +1 -0
- package/dist/contract/contracts-lock-file.js.map +1 -1
- package/dist/contract/derive-free-feature.d.ts +19 -0
- package/dist/contract/derive-free-feature.d.ts.map +1 -1
- package/dist/contract/derive-free-feature.js +40 -2
- package/dist/contract/derive-free-feature.js.map +1 -1
- package/dist/contract/project-contract-blockers.d.ts.map +1 -1
- package/dist/contract/project-contract-blockers.js +4 -2
- package/dist/contract/project-contract-blockers.js.map +1 -1
- package/dist/contract/record-contract-approval.d.ts +16 -1
- package/dist/contract/record-contract-approval.d.ts.map +1 -1
- package/dist/contract/record-contract-approval.js +29 -1
- package/dist/contract/record-contract-approval.js.map +1 -1
- package/dist/contract/render-free-gherkin.d.ts +2 -0
- package/dist/contract/render-free-gherkin.d.ts.map +1 -1
- package/dist/contract/render-free-gherkin.js +6 -0
- package/dist/contract/render-free-gherkin.js.map +1 -1
- package/dist/contract/resolve-unit-trace-identities.d.ts +12 -0
- package/dist/contract/resolve-unit-trace-identities.d.ts.map +1 -1
- package/dist/contract/resolve-unit-trace-identities.js +33 -49
- package/dist/contract/resolve-unit-trace-identities.js.map +1 -1
- package/dist/contract/trace/collect-trace-evidence.d.ts +16 -5
- package/dist/contract/trace/collect-trace-evidence.d.ts.map +1 -1
- package/dist/contract/trace/collect-trace-evidence.js +101 -43
- package/dist/contract/trace/collect-trace-evidence.js.map +1 -1
- package/dist/contract/trace/read-file-once.d.ts +20 -0
- package/dist/contract/trace/read-file-once.d.ts.map +1 -0
- package/dist/contract/trace/read-file-once.js +135 -0
- package/dist/contract/trace/read-file-once.js.map +1 -0
- package/dist/contract/trace/scenario-id-claims.d.ts +11 -0
- package/dist/contract/trace/scenario-id-claims.d.ts.map +1 -0
- package/dist/contract/trace/scenario-id-claims.js +89 -0
- package/dist/contract/trace/scenario-id-claims.js.map +1 -0
- package/dist/contract/trace/select-final-attempt.d.ts +4 -2
- package/dist/contract/trace/select-final-attempt.d.ts.map +1 -1
- package/dist/contract/trace/select-final-attempt.js +7 -4
- package/dist/contract/trace/select-final-attempt.js.map +1 -1
- package/dist/contract/trace/test-step-join.d.ts +3 -3
- package/dist/contract/trace/test-step-join.d.ts.map +1 -1
- package/dist/contract/trace/test-step-join.js +2 -2
- package/dist/contract/trace/test-step-join.js.map +1 -1
- package/dist/contract/trace/trace-binding.d.ts +3 -2
- package/dist/contract/trace/trace-binding.d.ts.map +1 -1
- package/dist/contract/trace/trace-binding.js +6 -3
- package/dist/contract/trace/trace-binding.js.map +1 -1
- package/dist/contract/trace/trace-file-reader.d.ts +14 -2
- package/dist/contract/trace/trace-file-reader.d.ts.map +1 -1
- package/dist/contract/trace/trace-file-reader.js +27 -45
- package/dist/contract/trace/trace-file-reader.js.map +1 -1
- package/dist/contract/trace/trace-freshness.d.ts +27 -0
- package/dist/contract/trace/trace-freshness.d.ts.map +1 -0
- package/dist/contract/trace/trace-freshness.js +146 -0
- package/dist/contract/trace/trace-freshness.js.map +1 -0
- package/dist/contract/trace/trace-identity-resolution.d.ts +2 -0
- package/dist/contract/trace/trace-identity-resolution.d.ts.map +1 -1
- package/dist/contract/trace/trace-identity-resolution.js +6 -3
- package/dist/contract/trace/trace-identity-resolution.js.map +1 -1
- package/dist/contract/trace/trace-run-record.d.ts +8 -3
- package/dist/contract/trace/trace-run-record.d.ts.map +1 -1
- package/dist/contract/trace/trace-run-record.js +42 -22
- package/dist/contract/trace/trace-run-record.js.map +1 -1
- package/dist/contract/trace/trace-steps.js +1 -1
- package/dist/contract/trace/trace-steps.js.map +1 -1
- package/dist/contract/trace/trace-zip-limits.d.ts +5 -1
- package/dist/contract/trace/trace-zip-limits.d.ts.map +1 -1
- package/dist/contract/trace/trace-zip-limits.js +6 -2
- package/dist/contract/trace/trace-zip-limits.js.map +1 -1
- package/dist/contract/verify-approval-decisions.d.ts.map +1 -1
- package/dist/contract/verify-approval-decisions.js +9 -4
- package/dist/contract/verify-approval-decisions.js.map +1 -1
- package/dist/decisions/confirmed-decision-commands.d.ts +11 -0
- package/dist/decisions/confirmed-decision-commands.d.ts.map +1 -1
- package/dist/decisions/confirmed-decision-commands.js +21 -3
- package/dist/decisions/confirmed-decision-commands.js.map +1 -1
- package/dist/decisions/decision-schema.d.ts +2 -1
- package/dist/decisions/decision-schema.d.ts.map +1 -1
- package/dist/decisions/decision-schema.js +2 -1
- package/dist/decisions/decision-schema.js.map +1 -1
- package/dist/decisions/git-head-trust.d.ts +12 -0
- package/dist/decisions/git-head-trust.d.ts.map +1 -1
- package/dist/decisions/git-head-trust.js +52 -0
- package/dist/decisions/git-head-trust.js.map +1 -1
- package/dist/decisions/human-confirmation.d.ts +2 -0
- package/dist/decisions/human-confirmation.d.ts.map +1 -1
- package/dist/decisions/human-confirmation.js +6 -0
- package/dist/decisions/human-confirmation.js.map +1 -1
- package/dist/decisions/legacy-waiver-migration.d.ts +8 -1
- package/dist/decisions/legacy-waiver-migration.d.ts.map +1 -1
- package/dist/decisions/legacy-waiver-migration.js +13 -4
- package/dist/decisions/legacy-waiver-migration.js.map +1 -1
- package/dist/exporters/csv-exporter.d.ts +11 -2
- package/dist/exporters/csv-exporter.d.ts.map +1 -1
- package/dist/exporters/csv-exporter.js +8 -4
- package/dist/exporters/csv-exporter.js.map +1 -1
- package/dist/exporters/matrix/export.d.ts +6 -1
- package/dist/exporters/matrix/export.d.ts.map +1 -1
- package/dist/exporters/matrix/export.js +11 -4
- package/dist/exporters/matrix/export.js.map +1 -1
- package/dist/exporters/matrix/xlsx-cells.js +1 -1
- package/dist/exporters/matrix/xlsx-cells.js.map +1 -1
- package/dist/exporters/xlsx-exporter.d.ts +3 -2
- package/dist/exporters/xlsx-exporter.d.ts.map +1 -1
- package/dist/exporters/xlsx-exporter.js +4 -4
- package/dist/exporters/xlsx-exporter.js.map +1 -1
- package/dist/graph/board-human-actions.d.ts +5 -0
- package/dist/graph/board-human-actions.d.ts.map +1 -0
- package/dist/graph/board-human-actions.js +83 -0
- package/dist/graph/board-human-actions.js.map +1 -0
- package/dist/graph/evaluate-graph.d.ts +21 -1
- package/dist/graph/evaluate-graph.d.ts.map +1 -1
- package/dist/graph/evaluate-graph.js +37 -9
- package/dist/graph/evaluate-graph.js.map +1 -1
- package/dist/graph/human-action.d.ts +21 -0
- package/dist/graph/human-action.d.ts.map +1 -0
- package/dist/graph/human-action.js +3 -0
- package/dist/graph/human-action.js.map +1 -0
- package/dist/graph/manual-triage-decisions.d.ts +19 -0
- package/dist/graph/manual-triage-decisions.d.ts.map +1 -0
- package/dist/graph/manual-triage-decisions.js +53 -0
- package/dist/graph/manual-triage-decisions.js.map +1 -0
- package/dist/graph/node-decl.d.ts +16 -0
- package/dist/graph/node-decl.d.ts.map +1 -1
- package/dist/graph/node-decl.js +4 -1
- package/dist/graph/node-decl.js.map +1 -1
- package/dist/graph/nodes/test-case-contract-nodes.d.ts +3 -1
- package/dist/graph/nodes/test-case-contract-nodes.d.ts.map +1 -1
- package/dist/graph/nodes/test-case-contract-nodes.js +22 -6
- package/dist/graph/nodes/test-case-contract-nodes.js.map +1 -1
- package/dist/graph/nodes/test-case-nodes.d.ts +3 -0
- package/dist/graph/nodes/test-case-nodes.d.ts.map +1 -1
- package/dist/graph/nodes/test-case-nodes.js +33 -4
- package/dist/graph/nodes/test-case-nodes.js.map +1 -1
- package/dist/graph/nodes/unit-audit-nodes.d.ts.map +1 -1
- package/dist/graph/nodes/unit-audit-nodes.js +1 -0
- package/dist/graph/nodes/unit-audit-nodes.js.map +1 -1
- package/dist/graph/nodes/unit-contract-node.d.ts +2 -1
- package/dist/graph/nodes/unit-contract-node.d.ts.map +1 -1
- package/dist/graph/nodes/unit-contract-node.js +16 -1
- package/dist/graph/nodes/unit-contract-node.js.map +1 -1
- package/dist/graph/nodes/unit-design-nodes.d.ts.map +1 -1
- package/dist/graph/nodes/unit-design-nodes.js +2 -1
- package/dist/graph/nodes/unit-design-nodes.js.map +1 -1
- package/dist/graph/test-case-results.d.ts +1 -0
- package/dist/graph/test-case-results.d.ts.map +1 -1
- package/dist/graph/test-case-results.js +27 -3
- package/dist/graph/test-case-results.js.map +1 -1
- package/dist/graph/test-case-satisfied.d.ts +5 -4
- package/dist/graph/test-case-satisfied.d.ts.map +1 -1
- package/dist/graph/test-case-satisfied.js +4 -0
- package/dist/graph/test-case-satisfied.js.map +1 -1
- package/dist/graph/test-case-state-shape.d.ts +7 -1
- package/dist/graph/test-case-state-shape.d.ts.map +1 -1
- package/dist/graph/unit-artifact-probes.d.ts.map +1 -1
- package/dist/graph/unit-artifact-probes.js +3 -1
- package/dist/graph/unit-artifact-probes.js.map +1 -1
- package/dist/graph/unit-results-freshness.d.ts +29 -0
- package/dist/graph/unit-results-freshness.d.ts.map +1 -0
- package/dist/graph/unit-results-freshness.js +213 -0
- package/dist/graph/unit-results-freshness.js.map +1 -0
- package/dist/harness/audit.d.ts +3 -2
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +4 -17
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/catalog/gate-rules.yaml +3 -3
- package/dist/harness/defect-draft.d.ts.map +1 -1
- package/dist/harness/defect-draft.js +12 -2
- package/dist/harness/defect-draft.js.map +1 -1
- package/dist/harness/defects.d.ts +14 -0
- package/dist/harness/defects.d.ts.map +1 -1
- package/dist/harness/defects.js +14 -10
- package/dist/harness/defects.js.map +1 -1
- package/dist/harness/flow-check.d.ts +9 -0
- package/dist/harness/flow-check.d.ts.map +1 -1
- package/dist/harness/flow-check.js +11 -0
- package/dist/harness/flow-check.js.map +1 -1
- package/dist/harness/gates/audit-gate-verdict.d.ts.map +1 -1
- package/dist/harness/gates/audit-gate-verdict.js +2 -1
- package/dist/harness/gates/audit-gate-verdict.js.map +1 -1
- package/dist/harness/gates/audit-input-hash.d.ts +2 -0
- package/dist/harness/gates/audit-input-hash.d.ts.map +1 -1
- package/dist/harness/gates/audit-input-hash.js +30 -2
- package/dist/harness/gates/audit-input-hash.js.map +1 -1
- package/dist/harness/gates/audit-report-verification.d.ts +17 -6
- package/dist/harness/gates/audit-report-verification.d.ts.map +1 -1
- package/dist/harness/gates/audit-report-verification.js +55 -21
- package/dist/harness/gates/audit-report-verification.js.map +1 -1
- package/dist/harness/gates/delivery-checklist.d.ts +3 -0
- package/dist/harness/gates/delivery-checklist.d.ts.map +1 -1
- package/dist/harness/gates/delivery-checklist.js +39 -6
- package/dist/harness/gates/delivery-checklist.js.map +1 -1
- package/dist/harness/gates/delivery-decision-gate.d.ts +4 -0
- package/dist/harness/gates/delivery-decision-gate.d.ts.map +1 -1
- package/dist/harness/gates/delivery-decision-gate.js +26 -1
- package/dist/harness/gates/delivery-decision-gate.js.map +1 -1
- package/dist/harness/gates/delivery-decision-step.d.ts +4 -1
- package/dist/harness/gates/delivery-decision-step.d.ts.map +1 -1
- package/dist/harness/gates/delivery-decision-step.js +17 -2
- package/dist/harness/gates/delivery-decision-step.js.map +1 -1
- package/dist/harness/gates/in-run-decision-check.d.ts.map +1 -1
- package/dist/harness/gates/in-run-decision-check.js +40 -12
- package/dist/harness/gates/in-run-decision-check.js.map +1 -1
- package/dist/harness/gates/migrated-waiver-import-bounds.d.ts +13 -0
- package/dist/harness/gates/migrated-waiver-import-bounds.d.ts.map +1 -0
- package/dist/harness/gates/migrated-waiver-import-bounds.js +141 -0
- package/dist/harness/gates/migrated-waiver-import-bounds.js.map +1 -0
- package/dist/harness/gates/migrated-waiver-review.d.ts +14 -4
- package/dist/harness/gates/migrated-waiver-review.d.ts.map +1 -1
- package/dist/harness/gates/migrated-waiver-review.js +78 -80
- package/dist/harness/gates/migrated-waiver-review.js.map +1 -1
- package/dist/harness/journey.d.ts +40 -0
- package/dist/harness/journey.d.ts.map +1 -1
- package/dist/harness/journey.js +155 -22
- package/dist/harness/journey.js.map +1 -1
- package/dist/harness/next-step.d.ts +19 -0
- package/dist/harness/next-step.d.ts.map +1 -1
- package/dist/harness/next-step.js +21 -0
- package/dist/harness/next-step.js.map +1 -1
- package/dist/harness/provenance.d.ts +29 -0
- package/dist/harness/provenance.d.ts.map +1 -1
- package/dist/harness/provenance.js +109 -9
- package/dist/harness/provenance.js.map +1 -1
- package/dist/harness/triage-manual-classification.d.ts +32 -0
- package/dist/harness/triage-manual-classification.d.ts.map +1 -0
- package/dist/harness/triage-manual-classification.js +56 -0
- package/dist/harness/triage-manual-classification.js.map +1 -0
- package/dist/harness/unit-paths.d.ts +8 -0
- package/dist/harness/unit-paths.d.ts.map +1 -1
- package/dist/harness/unit-paths.js +23 -1
- package/dist/harness/unit-paths.js.map +1 -1
- package/dist/hooks/bash-read-only-segment.d.ts +9 -0
- package/dist/hooks/bash-read-only-segment.d.ts.map +1 -0
- package/dist/hooks/bash-read-only-segment.js +168 -0
- package/dist/hooks/bash-read-only-segment.js.map +1 -0
- package/dist/hooks/bash-write-targets.d.ts +12 -2
- package/dist/hooks/bash-write-targets.d.ts.map +1 -1
- package/dist/hooks/bash-write-targets.js +122 -29
- package/dist/hooks/bash-write-targets.js.map +1 -1
- package/dist/hooks/guard-protected-paths.d.ts +4 -0
- package/dist/hooks/guard-protected-paths.d.ts.map +1 -1
- package/dist/hooks/guard-protected-paths.js +13 -3
- package/dist/hooks/guard-protected-paths.js.map +1 -1
- package/dist/hooks/guard-rules.d.ts +0 -17
- package/dist/hooks/guard-rules.d.ts.map +1 -1
- package/dist/hooks/guard-rules.js +79 -10
- package/dist/hooks/guard-rules.js.map +1 -1
- package/dist/hooks/guard-tty-spoof-rule.d.ts +3 -1
- package/dist/hooks/guard-tty-spoof-rule.d.ts.map +1 -1
- package/dist/hooks/guard-tty-spoof-rule.js +3 -2
- package/dist/hooks/guard-tty-spoof-rule.js.map +1 -1
- package/dist/hooks/hand-back-core.d.ts +9 -4
- package/dist/hooks/hand-back-core.d.ts.map +1 -1
- package/dist/hooks/hand-back-core.js +24 -11
- package/dist/hooks/hand-back-core.js.map +1 -1
- package/dist/hooks/session-start-unit-state-hook.d.ts +2 -1
- package/dist/hooks/session-start-unit-state-hook.d.ts.map +1 -1
- package/dist/hooks/session-start-unit-state-hook.js +9 -4
- package/dist/hooks/session-start-unit-state-hook.js.map +1 -1
- package/dist/hooks/stop-next-step-hook.d.ts.map +1 -1
- package/dist/hooks/stop-next-step-hook.js +1 -0
- package/dist/hooks/stop-next-step-hook.js.map +1 -1
- package/dist/orchestrator/assets-drift.d.ts +5 -0
- package/dist/orchestrator/assets-drift.d.ts.map +1 -1
- package/dist/orchestrator/assets-drift.js +12 -3
- package/dist/orchestrator/assets-drift.js.map +1 -1
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +1 -0
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/commands/create-test.md +6 -3
- package/dist/orchestrator/templates/ai-src/commands/delivery.md +2 -0
- package/dist/orchestrator/templates/ai-src/commands/review.md +2 -0
- package/dist/orchestrator/templates/ai-src/commands/run-test.md +1 -1
- package/dist/orchestrator/templates/ai-src/partials/waiting-for-you.claude.md +11 -0
- package/dist/orchestrator/templates/ai-src/partials/waiting-for-you.codex.md +11 -0
- package/dist/orchestrator/templates/ai-src/partials/waiting-for-you.copilot.md +11 -0
- package/package.json +3 -3
- package/src/capabilities/discover.ts +15 -2
- package/src/cli/case-triage-hint.ts +31 -0
- package/src/cli/commands/audit.ts +1 -1
- package/src/cli/commands/contract-derive-command.ts +20 -8
- package/src/cli/commands/contract.ts +41 -7
- package/src/cli/commands/delivery.ts +56 -18
- package/src/cli/commands/gate.ts +2 -1
- package/src/cli/commands/generate.ts +13 -2
- package/src/cli/commands/journey.ts +8 -6
- package/src/cli/commands/next.ts +41 -12
- package/src/cli/commands/triage.ts +41 -3
- package/src/cli/commands/update.ts +6 -2
- package/src/cli/index.ts +5 -1
- package/src/cli/json-error-on-exit.ts +72 -0
- package/src/cli/project-sungen-invocation.ts +59 -0
- package/src/cli/run-command-hints.ts +47 -0
- package/src/cli/waiting-for-you-block.ts +27 -0
- package/src/contract/accept-opaque-steps.ts +114 -0
- package/src/contract/approve-contract.ts +10 -3
- package/src/contract/build-contract-verdict.ts +2 -2
- package/src/contract/build-trace-conformance.ts +11 -8
- package/src/contract/classify-contract-change.ts +3 -3
- package/src/contract/classify-trace-conformance.ts +76 -22
- package/src/contract/contract-schema.ts +31 -2
- package/src/contract/contracts-lock-file.ts +1 -0
- package/src/contract/derive-free-feature.ts +46 -3
- package/src/contract/project-contract-blockers.ts +4 -2
- package/src/contract/record-contract-approval.ts +41 -1
- package/src/contract/render-free-gherkin.ts +5 -0
- package/src/contract/resolve-unit-trace-identities.ts +35 -12
- package/src/contract/trace/collect-trace-evidence.ts +105 -36
- package/src/contract/trace/read-file-once.ts +95 -0
- package/src/contract/trace/scenario-id-claims.ts +60 -0
- package/src/contract/trace/select-final-attempt.ts +12 -5
- package/src/contract/trace/test-step-join.ts +6 -5
- package/src/contract/trace/trace-binding.ts +7 -4
- package/src/contract/trace/trace-file-reader.ts +34 -13
- package/src/contract/trace/trace-freshness.ts +110 -0
- package/src/contract/trace/trace-identity-resolution.ts +7 -4
- package/src/contract/trace/trace-run-record.ts +37 -15
- package/src/contract/trace/trace-steps.ts +1 -1
- package/src/contract/trace/trace-zip-limits.ts +6 -2
- package/src/contract/verify-approval-decisions.ts +9 -5
- package/src/decisions/confirmed-decision-commands.ts +25 -4
- package/src/decisions/decision-schema.ts +2 -1
- package/src/decisions/git-head-trust.ts +44 -0
- package/src/decisions/human-confirmation.ts +5 -0
- package/src/decisions/legacy-waiver-migration.ts +14 -3
- package/src/exporters/csv-exporter.ts +12 -4
- package/src/exporters/matrix/export.ts +11 -4
- package/src/exporters/matrix/xlsx-cells.ts +1 -1
- package/src/exporters/xlsx-exporter.ts +5 -4
- package/src/graph/board-human-actions.ts +80 -0
- package/src/graph/evaluate-graph.ts +45 -10
- package/src/graph/human-action.ts +33 -0
- package/src/graph/manual-triage-decisions.ts +55 -0
- package/src/graph/node-decl.ts +18 -0
- package/src/graph/nodes/test-case-contract-nodes.ts +28 -7
- package/src/graph/nodes/test-case-nodes.ts +37 -5
- package/src/graph/nodes/unit-audit-nodes.ts +2 -1
- package/src/graph/nodes/unit-contract-node.ts +20 -2
- package/src/graph/nodes/unit-design-nodes.ts +2 -1
- package/src/graph/test-case-results.ts +25 -4
- package/src/graph/test-case-satisfied.ts +7 -4
- package/src/graph/test-case-state-shape.ts +7 -2
- package/src/graph/unit-artifact-probes.ts +3 -1
- package/src/graph/unit-results-freshness.ts +165 -0
- package/src/harness/audit.ts +4 -13
- package/src/harness/catalog/gate-rules.yaml +3 -3
- package/src/harness/defect-draft.ts +13 -2
- package/src/harness/defects.ts +22 -9
- package/src/harness/flow-check.ts +11 -0
- package/src/harness/gates/audit-gate-verdict.ts +2 -1
- package/src/harness/gates/audit-input-hash.ts +27 -3
- package/src/harness/gates/audit-report-verification.ts +57 -24
- package/src/harness/gates/delivery-checklist.ts +39 -7
- package/src/harness/gates/delivery-decision-gate.ts +29 -1
- package/src/harness/gates/delivery-decision-step.ts +19 -3
- package/src/harness/gates/in-run-decision-check.ts +38 -13
- package/src/harness/gates/migrated-waiver-import-bounds.ts +103 -0
- package/src/harness/gates/migrated-waiver-review.ts +90 -37
- package/src/harness/journey.ts +174 -21
- package/src/harness/next-step.ts +34 -1
- package/src/harness/provenance.ts +99 -11
- package/src/harness/triage-manual-classification.ts +76 -0
- package/src/harness/unit-paths.ts +19 -0
- package/src/hooks/bash-read-only-segment.ts +130 -0
- package/src/hooks/bash-write-targets.ts +109 -23
- package/src/hooks/guard-protected-paths.ts +13 -3
- package/src/hooks/guard-rules.ts +37 -18
- package/src/hooks/guard-tty-spoof-rule.ts +6 -3
- package/src/hooks/hand-back-core.ts +24 -12
- package/src/hooks/session-start-unit-state-hook.ts +9 -4
- package/src/hooks/stop-next-step-hook.ts +1 -0
- package/src/orchestrator/assets-drift.ts +12 -5
- package/src/orchestrator/project-initializer.ts +1 -0
- package/src/orchestrator/templates/ai-src/commands/create-test.md +6 -3
- package/src/orchestrator/templates/ai-src/commands/delivery.md +2 -0
- package/src/orchestrator/templates/ai-src/commands/review.md +2 -0
- package/src/orchestrator/templates/ai-src/commands/run-test.md +1 -1
- package/src/orchestrator/templates/ai-src/partials/waiting-for-you.claude.md +11 -0
- package/src/orchestrator/templates/ai-src/partials/waiting-for-you.codex.md +11 -0
- package/src/orchestrator/templates/ai-src/partials/waiting-for-you.copilot.md +11 -0
|
@@ -7,62 +7,115 @@
|
|
|
7
7
|
*
|
|
8
8
|
* vouched HEAD's state file holds the record (same reason, `at`, auditHashAtWaive) — reviewedAt is
|
|
9
9
|
* the commit time of the oldest commit in the unbroken run, back from HEAD, that holds it
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* imported the record could have been the import's source (#925) — always checked, run start or not:
|
|
11
|
+
* committed strictly before the decision file, the unit's import marker and the project's
|
|
12
|
+
* adoption of 3.3 first appeared (migrated-waiver-import-bounds.ts). Failing one, the decision
|
|
13
|
+
* claims a migration that never happened → in-run, even at `gate --phase deliver`, which has
|
|
14
|
+
* no run start.
|
|
15
|
+
* with a run start also in-run when reviewedAt is after it (the same test as any committed decision)
|
|
13
16
|
* Anything else — no git, no such record in HEAD, a record that differs — is not vouched for: the
|
|
14
|
-
* decision is judged as any other (`not in git HEAD`, or its own commit time).
|
|
17
|
+
* decision is judged as any other (`not in git HEAD`, or its own commit time). Only a rewritten history
|
|
18
|
+
* (a squash or rebase that folds the record's commit into the import's) can fail a genuine import; the
|
|
19
|
+
* finding says which bound it crossed.
|
|
20
|
+
*
|
|
21
|
+
* Nor is a record whose introducing commit is out of reach — judged as any other, with `unvouched`
|
|
22
|
+
* saying why, never vouched for by a later commit taken for its introduction: the walk reads at most
|
|
23
|
+
* MAX_COMMITS commits of a state file and the record is held by the commit before them too (#924), or
|
|
24
|
+
* the record reaches a shallow clone's boundary commit. One review reads each state file's history
|
|
25
|
+
* once (one `git log`, one `git cat-file --batch`), however many waivers share it.
|
|
15
26
|
*/
|
|
16
|
-
import
|
|
17
|
-
import * as path from 'path';
|
|
18
|
-
import { committedText, gitOutput } from '../../decisions/git-head-trust';
|
|
27
|
+
import { gitFileTexts, gitOutput, isShallowRepository } from '../../decisions/git-head-trust';
|
|
19
28
|
import type { ReadDecision } from '../../decisions/read-decisions';
|
|
20
29
|
import { asLegacyWaiver, isLegacyWaiver, sameWaiver } from '../../decisions/waiver-decisions';
|
|
21
30
|
import { reportSlug } from '../unit-paths';
|
|
31
|
+
import { migratedWaiverImportBounds, migratedWaiverRemedy } from './migrated-waiver-import-bounds';
|
|
22
32
|
|
|
23
|
-
/** Commits of one state file the walk reads at most
|
|
33
|
+
/** Commits of one state file the walk reads at most. */
|
|
24
34
|
const MAX_COMMITS = 200;
|
|
25
35
|
|
|
26
|
-
|
|
36
|
+
type State = { waivers?: Record<string, unknown> } | null;
|
|
37
|
+
/** A state file in git: HEAD's version and its newest MAX_COMMITS + 1 commits (newest first; `root`: no parent shown). */
|
|
38
|
+
interface StateHistory { rel: string; head: State; commits: Array<{ at: string; state: State; root: boolean }> }
|
|
39
|
+
|
|
40
|
+
/** A migrated waiver HEAD's legacy file holds: when it was reviewed, or why that cannot be said. */
|
|
41
|
+
export interface MigratedReview {
|
|
42
|
+
/** When its legacy record was committed; null when that commit is out of reach (see `unvouched`). */
|
|
43
|
+
at: string | null;
|
|
44
|
+
/** Set with `at`: why the record cannot have been the import's source — the decision is in-run. */
|
|
45
|
+
notImported?: string;
|
|
46
|
+
/** Set when `at` is null: why the record vouches for nothing — the decision is judged as any other. */
|
|
47
|
+
unvouched?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const parse = (text: string | null): State => {
|
|
27
51
|
if (text === null) return null;
|
|
28
52
|
try { return JSON.parse(text.replace(/^/, '')); } catch { return null; }
|
|
29
53
|
};
|
|
30
54
|
|
|
31
|
-
function holds(state:
|
|
55
|
+
function holds(state: State, e: ReadDecision, ob: string): boolean {
|
|
32
56
|
const w = state?.waivers?.[ob];
|
|
33
57
|
return isLegacyWaiver(w) && w.at === e.valid_from && sameWaiver(w, asLegacyWaiver(e));
|
|
34
58
|
}
|
|
35
59
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
return oldest && !Number.isNaN(Date.parse(oldest)) ? oldest : null;
|
|
60
|
+
function readHistory(projectRoot: string, rel: string): StateHistory {
|
|
61
|
+
const log = (gitOutput(projectRoot, ['log', `--max-count=${MAX_COMMITS + 1}`, '--format=%H %cI %P', '--', rel]) ?? '')
|
|
62
|
+
.split('\n').filter(Boolean).map((line) => line.trim().split(' '));
|
|
63
|
+
const texts = gitFileTexts(projectRoot, [`HEAD:./${rel}`, ...log.map(([sha]) => `${sha}:./${rel}`)]);
|
|
64
|
+
return {
|
|
65
|
+
rel,
|
|
66
|
+
head: parse(texts?.[0] ?? null),
|
|
67
|
+
commits: log.map(([, at, ...parents], i) => ({ at, state: parse(texts?.[i + 1] ?? null), root: parents.length === 0 })),
|
|
68
|
+
};
|
|
46
69
|
}
|
|
47
70
|
|
|
48
|
-
/**
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
71
|
+
/**
|
|
72
|
+
* The commit time of the oldest commit, in the unbroken run back from HEAD, whose state file holds the
|
|
73
|
+
* record. The walk reads MAX_COMMITS commits; the next one only says whether the last of them introduced it.
|
|
74
|
+
* In a shallow clone a commit with no parent shown is the boundary: every file seems to appear there, so a
|
|
75
|
+
* record reaching it has no known commit time.
|
|
76
|
+
*/
|
|
77
|
+
function introducedAt(h: StateHistory, e: ReadDecision, ob: string, scope: string, shallow: () => boolean): MigratedReview | null {
|
|
78
|
+
let run = 0;
|
|
79
|
+
while (run < h.commits.length && holds(h.commits[run].state, e, ob)) run++;
|
|
80
|
+
const unvouched = (why: string): MigratedReview => ({ at: null, unvouched: `migrated, but its legacy record cannot vouch for it: ${why}` });
|
|
81
|
+
if (run > MAX_COMMITS) {
|
|
82
|
+
return unvouched(`${h.rel} changed in more than ${MAX_COMMITS} commits, and the commit that introduced the record is older than all of them`);
|
|
59
83
|
}
|
|
60
|
-
|
|
84
|
+
const oldest = run ? h.commits[run - 1] : null;
|
|
85
|
+
if (oldest?.root && shallow()) {
|
|
86
|
+
return unvouched(`the git history is shallow, so the record's real commit time is unknown — fetch the full history `
|
|
87
|
+
+ `(\`git fetch --unshallow\`; \`fetch-depth: 0\` in CI), or ${migratedWaiverRemedy(scope, ob)}`);
|
|
88
|
+
}
|
|
89
|
+
return oldest && !Number.isNaN(Date.parse(oldest.at)) ? { at: oldest.at } : null;
|
|
61
90
|
}
|
|
62
91
|
|
|
63
|
-
/**
|
|
64
|
-
export function
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
92
|
+
/** One run's reads for migrated waivers: each state file's history, decision's first commit and import marker, read once. */
|
|
93
|
+
export function migratedWaiverReview(projectRoot: string) {
|
|
94
|
+
const histories = new Map<string, StateHistory>();
|
|
95
|
+
const bounds = migratedWaiverImportBounds(projectRoot);
|
|
96
|
+
let isShallow: boolean | undefined;
|
|
97
|
+
const shallow = (): boolean => (isShallow ??= isShallowRepository(projectRoot));
|
|
98
|
+
const historyOf = (rel: string): StateHistory => {
|
|
99
|
+
if (!histories.has(rel)) histories.set(rel, readHistory(projectRoot, rel));
|
|
100
|
+
return histories.get(rel)!;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
/** How a migrated waiver was reviewed; null when it is not one HEAD's legacy file holds (judged as any other). */
|
|
105
|
+
reviewOf(scope: string, e: ReadDecision): MigratedReview | null {
|
|
106
|
+
const ob = e.payload?.obligation;
|
|
107
|
+
if (e.kind !== 'waiver' || e.source?.where !== 'migrated' || typeof ob !== 'string') return null;
|
|
108
|
+
// The journey keys its state by the unit name it was given: `<name>`, or the catalog id for api units.
|
|
109
|
+
const slugs = new Set([scope.split('/').pop() ?? '', reportSlug(scope.replace(/^(screens|flows)\//, ''))]);
|
|
110
|
+
let unvouched: MigratedReview | null = null;
|
|
111
|
+
for (const slug of slugs) {
|
|
112
|
+
const h = historyOf(`.sungen/journey/${slug}.state.json`);
|
|
113
|
+
if (!holds(h.head, e, ob)) continue;
|
|
114
|
+
const review = introducedAt(h, e, ob, scope, shallow);
|
|
115
|
+
if (review?.at) return { at: review.at, notImported: bounds.notImported(slug, e, review.at) };
|
|
116
|
+
unvouched = unvouched ?? review;
|
|
117
|
+
}
|
|
118
|
+
return unvouched;
|
|
119
|
+
},
|
|
120
|
+
};
|
|
68
121
|
}
|
package/src/harness/journey.ts
CHANGED
|
@@ -20,8 +20,9 @@ import { readTextFile } from './read-text';
|
|
|
20
20
|
import { assertNoPendingMigration } from '../basis/migration-sentinel';
|
|
21
21
|
import { commitWaiverSync, planWaiverSync, recordWaiver, untrustedWaiverText } from '../decisions/legacy-waiver-migration';
|
|
22
22
|
import { DecisionConflictError } from '../decisions/append-decision';
|
|
23
|
+
import { gitUserName } from '../decisions/decision-source';
|
|
23
24
|
import { resolveRule } from './gates/resolve-rule';
|
|
24
|
-
import { applyAuditOverrides, decisionFindingText, DecisionGateFinding, ignoredOverrideText, journeyDecisionFindings, readJourneyAudit, unitOverrides } from './gates/journey-gate-inputs';
|
|
25
|
+
import { applyAuditOverrides, decisionFindingText, DecisionGateFinding, ignoredOverrideText, journeyDecisionFindings, OBLIGATION_RULE, readJourneyAudit, unitOverrides } from './gates/journey-gate-inputs';
|
|
25
26
|
import { IN_RUN_RULE, uncommittedReleaseDecisions } from './gates/in-run-decision-check';
|
|
26
27
|
import { PROJECT_SCOPE } from '../decisions/decision-file';
|
|
27
28
|
|
|
@@ -85,6 +86,26 @@ function extractAnchor(text: string): Anchor {
|
|
|
85
86
|
|
|
86
87
|
const SAT = 0.8; // axis at/above this = satisfied (below = needs-work)
|
|
87
88
|
|
|
89
|
+
/**
|
|
90
|
+
* OB-coverage IS the audit's viewpoint gate (#934): the registry maps it to `audit.viewpoint-coverage`,
|
|
91
|
+
* whose condition is "no critical theme missing or shallow" (audit-gate-terms.ts). The coverage
|
|
92
|
+
* percentage alone can read 83% with a critical theme missing, so a gap the rule blocks on keeps the
|
|
93
|
+
* obligation open and names the themes. An override the audit applied is credited later by
|
|
94
|
+
* applyAuditOverrides — journey never re-applies one, so the rule is resolved from the catalog here.
|
|
95
|
+
*/
|
|
96
|
+
function coverageObligation(coverage: number | undefined, audit: any): Obligation {
|
|
97
|
+
const id = 'OB-coverage';
|
|
98
|
+
const title = 'Viewpoint coverage';
|
|
99
|
+
const pct = coverage === undefined ? '' : `${Math.round(coverage * 100)}% (need ${Math.round(SAT * 100)}%) — `;
|
|
100
|
+
const gaps: Array<{ theme?: unknown; status?: unknown }> = Array.isArray(audit.gate?.gaps) ? audit.gate.gaps : [];
|
|
101
|
+
if (gaps.length && resolveRule(OBLIGATION_RULE[id]).blocking) {
|
|
102
|
+
const themes = gaps.map((g) => `${String(g.theme)} (${g.status === 'shallow' ? 'shallow' : 'missing'})`).join(', ');
|
|
103
|
+
return { id, title, status: 'needs-work', detail: `${pct}critical theme(s) not covered: ${themes} — the audit's viewpoint gate fails on them` };
|
|
104
|
+
}
|
|
105
|
+
const status: ObStatus = coverage === undefined ? 'pending' : coverage >= SAT ? 'satisfied' : 'needs-work';
|
|
106
|
+
return { id, title, status, detail: `${pct}critical themes covered` };
|
|
107
|
+
}
|
|
108
|
+
|
|
88
109
|
function computeFresh(projectRoot: string, unit: string): JourneyReport {
|
|
89
110
|
const slug = reportSlug(unit);
|
|
90
111
|
// T3: the report is an output — read through the checks; stale or untrusted counts as never audited.
|
|
@@ -123,7 +144,7 @@ function computeFresh(projectRoot: string, unit: string): JourneyReport {
|
|
|
123
144
|
});
|
|
124
145
|
|
|
125
146
|
obligations.push(ob('OB-spec', 'Spec readiness', ax.specFR, 1, 'FR/sections sufficient to design from'));
|
|
126
|
-
obligations.push(
|
|
147
|
+
obligations.push(coverageObligation(ax.coverage, audit));
|
|
127
148
|
obligations.push(ob('OB-depth', 'Assertion depth', ax.businessDepth, depthThreshold, 'business-critical scenarios assert data'));
|
|
128
149
|
obligations.push(ob('OB-trace', 'Traceability', ax.taxonomy ?? ax.traceability, SAT, 'scenarios trace to viewpoints'));
|
|
129
150
|
|
|
@@ -180,7 +201,8 @@ function computeFresh(projectRoot: string, unit: string): JourneyReport {
|
|
|
180
201
|
// ---------------- S2: writable lifecycle — persisted waivers + reconcile ----------------
|
|
181
202
|
|
|
182
203
|
// Waivers live in the decision layer (DOS-13.5); this file keeps a dual-written copy for one release.
|
|
183
|
-
|
|
204
|
+
/** S5 — stays here in P0. `by` is who signs (--by, else the git user); `recordedBy` the git user when --by differs. */
|
|
205
|
+
interface Signoff { by?: string; recordedBy?: string; at: string; auditHashAtSignoff: string; }
|
|
184
206
|
interface JourneyState { unit: string; auditHash: string; waivers: Record<string, unknown>; signoff?: Signoff; }
|
|
185
207
|
|
|
186
208
|
function statePath(projectRoot: string, slug: string): string {
|
|
@@ -221,6 +243,52 @@ function computeNext(r: JourneyReport, unit: string): string {
|
|
|
221
243
|
* A read-only checkout gets the same board without throwing.
|
|
222
244
|
*/
|
|
223
245
|
export function runJourney(projectRoot: string, unit: string): JourneyReport {
|
|
246
|
+
const r = reconcileJourney(projectRoot, unit);
|
|
247
|
+
try {
|
|
248
|
+
commitWaiverSync(projectRoot, unit, r.slug, r.sync, r.statePath, JSON.stringify(r.next, null, 2));
|
|
249
|
+
} catch (err) {
|
|
250
|
+
if (!(err instanceof DecisionConflictError)) throw err;
|
|
251
|
+
const rel = path.relative(projectRoot, err.path).split(path.sep).join('/');
|
|
252
|
+
if (!r.decisionFindings.some((f) => f.path === rel)) r.decisionFindings.push({ rule: 'decision.mutated', path: rel, message: err.message });
|
|
253
|
+
}
|
|
254
|
+
return finishReport(r, unit);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** A waiver the board asks a human to give again: its evidence moved (stale), or git HEAD does not hold it (untrusted). */
|
|
258
|
+
export interface WaiverToRedecide { obligation: string; title: string; reason: string; why: 'stale' | 'untrusted' }
|
|
259
|
+
|
|
260
|
+
/** What the board asks of a human, read without writing anything (`sungen next`'s "Waiting for you", #954). */
|
|
261
|
+
export interface JourneyHumanAsks {
|
|
262
|
+
report: JourneyReport;
|
|
263
|
+
redecide: WaiverToRedecide[];
|
|
264
|
+
/** OB-signoff: `pending` (never given), `stale` (given before the audit changed), `given`. */
|
|
265
|
+
signoff: { status: 'pending' | 'stale' | 'given'; at?: string };
|
|
266
|
+
/** `sungen journey --signoff` would be accepted now (assertCanSignOff holds). */
|
|
267
|
+
canSignOff: boolean;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** The board's asks, read-only: no import, no dual-write, no state file — the same reconcile `runJourney` commits. */
|
|
271
|
+
export function readJourneyHumanAsks(projectRoot: string, unit: string): JourneyHumanAsks {
|
|
272
|
+
const r = reconcileJourney(projectRoot, unit);
|
|
273
|
+
const report = finishReport(r, unit);
|
|
274
|
+
let canSignOff = report.obligations.some((o) => o.id === SIGNOFF_OBLIGATION);
|
|
275
|
+
try { assertCanSignOff(report); } catch { canSignOff = false; }
|
|
276
|
+
return { report, redecide: r.redecide, signoff: r.signoff, canSignOff };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
interface Reconciled {
|
|
280
|
+
report: JourneyReport;
|
|
281
|
+
slug: string;
|
|
282
|
+
statePath: string;
|
|
283
|
+
sync: ReturnType<typeof planWaiverSync>;
|
|
284
|
+
next: JourneyState;
|
|
285
|
+
decisionFindings: DecisionGateFinding[];
|
|
286
|
+
redecide: WaiverToRedecide[];
|
|
287
|
+
signoff: JourneyHumanAsks['signoff'];
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** Compute fresh, apply the waivers and the sign-off in force — read-only; `runJourney` commits the plan. */
|
|
291
|
+
function reconcileJourney(projectRoot: string, unit: string): Reconciled {
|
|
224
292
|
const slug = reportSlug(unit);
|
|
225
293
|
assertNoPendingMigration(projectRoot, slug);
|
|
226
294
|
const report = computeFresh(projectRoot, unit);
|
|
@@ -228,43 +296,52 @@ export function runJourney(projectRoot: string, unit: string): JourneyReport {
|
|
|
228
296
|
const state = loadState(sp) || { unit, auditHash: '', waivers: {} };
|
|
229
297
|
const curHash = auditHashOf(projectRoot, slug);
|
|
230
298
|
const sync = planWaiverSync(projectRoot, unit, slug, sp, state.waivers);
|
|
299
|
+
const redecide: WaiverToRedecide[] = [];
|
|
231
300
|
for (const u of sync.untrusted) {
|
|
232
301
|
const title = report.obligations.find((o) => o.id === u.obligation)?.title ?? u.obligation;
|
|
233
|
-
|
|
302
|
+
const ignored = unwaivableWhy(u.obligation, unit);
|
|
303
|
+
if (!ignored) redecide.push({ obligation: u.obligation, title, reason: u.waiver.reason, why: 'untrusted' });
|
|
304
|
+
report.needsYou.unshift({ text: ignored ? ignoredWaiverText(u.obligation, ignored, u.waiver.reason) : untrustedWaiverText(unit, slug, title, u), anchor: { vpId: u.obligation } });
|
|
234
305
|
}
|
|
235
306
|
|
|
236
307
|
for (const ob of report.obligations) {
|
|
237
308
|
const w = sync.waivers[ob.id];
|
|
238
309
|
if (!w) continue;
|
|
310
|
+
// A waiver recorded before `--waive` refused this obligation never opens it (review W1).
|
|
311
|
+
const ignored = unwaivableWhy(ob.id, unit);
|
|
312
|
+
if (ignored) { report.needsYou.unshift({ text: ignoredWaiverText(ob.id, ignored, w.reason), anchor: { vpId: ob.id } }); continue; }
|
|
239
313
|
if (w.auditHashAtWaive === curHash) {
|
|
240
314
|
ob.status = 'waived';
|
|
241
315
|
ob.waivedReason = w.reason;
|
|
242
316
|
ob.detail = `waived — ${w.reason}`;
|
|
243
317
|
} else {
|
|
318
|
+
redecide.push({ obligation: ob.id, title: ob.title, reason: w.reason, why: 'stale' });
|
|
244
319
|
report.needsYou.unshift({ text: `⚠️ Waiver on "${ob.title}" is STALE (evidence changed since ${w.at}) — re-decide. Was: ${w.reason}`, anchor: { vpId: ob.id } });
|
|
245
320
|
}
|
|
246
321
|
}
|
|
247
322
|
// S5 — sign-off reconcile: a recorded sign-off satisfies OB-signoff while its evidence holds;
|
|
248
323
|
// if the audit changed since, the sign-off is STALE → re-surface for re-review (anti-amnesia).
|
|
249
324
|
const signoffOb = report.obligations.find((o) => o.id === 'OB-signoff');
|
|
325
|
+
let signoff: Reconciled['signoff'] = { status: 'pending' };
|
|
250
326
|
if (signoffOb && state.signoff) {
|
|
251
327
|
if (state.signoff.auditHashAtSignoff === curHash) {
|
|
328
|
+
signoff = { status: 'given', at: state.signoff.at };
|
|
252
329
|
signoffOb.status = 'satisfied';
|
|
253
|
-
signoffOb.detail = `signed off${state.signoff.by ? ' by ' + state.signoff.by : ''} at ${state.signoff.at}`;
|
|
330
|
+
signoffOb.detail = `signed off${state.signoff.by ? ' by ' + state.signoff.by : ''}${state.signoff.recordedBy ? ` (recorded by ${state.signoff.recordedBy})` : ''} at ${state.signoff.at}`;
|
|
254
331
|
} else {
|
|
332
|
+
signoff = { status: 'stale', at: state.signoff.at };
|
|
255
333
|
report.needsYou.unshift({ text: `⚠️ Sign-off is STALE (suite changed since ${state.signoff.at}) — re-review and sign off again.`, anchor: { vpId: 'OB-signoff' } });
|
|
256
334
|
}
|
|
257
335
|
}
|
|
258
336
|
// Phase-03 carry-over: a decision that does not verify is a FINDING the gate blocks on, not a crash.
|
|
259
337
|
const decisionFindings = journeyDecisionFindings(projectRoot, unitOverrides(projectRoot, unit));
|
|
260
338
|
const next: JourneyState = { unit, auditHash: curHash, waivers: sync.stateWaivers, signoff: state.signoff };
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
339
|
+
return { report, slug, statePath: sp, sync, next, decisionFindings, redecide, signoff };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/** The decision findings on the board, then the next suggestion — after the waivers and the sign-off. */
|
|
343
|
+
function finishReport(r: Reconciled, unit: string): JourneyReport {
|
|
344
|
+
const { report, decisionFindings } = r;
|
|
268
345
|
if (decisionFindings.length) {
|
|
269
346
|
report.decisionFindings = decisionFindings;
|
|
270
347
|
for (const f of [...decisionFindings].reverse()) report.needsYou.unshift({ text: decisionFindingText(f), anchor: { file: f.path } });
|
|
@@ -273,6 +350,42 @@ export function runJourney(projectRoot: string, unit: string): JourneyReport {
|
|
|
273
350
|
return report;
|
|
274
351
|
}
|
|
275
352
|
|
|
353
|
+
/** The single human gate (DOS §3.4, S5): given with `--signoff`, never waived (#936). */
|
|
354
|
+
export const SIGNOFF_OBLIGATION = 'OB-signoff';
|
|
355
|
+
export const signoffCommand = (unit: string): string => `sungen journey --screen ${unit} --signoff --by "<name>"`;
|
|
356
|
+
|
|
357
|
+
/** "Never audited" (T3): a unit is measured by `sungen audit`, never waived through (review W2). */
|
|
358
|
+
export const CREATE_OBLIGATION = 'OB-create';
|
|
359
|
+
|
|
360
|
+
/** Why a waiver can never accept `obId` (and what does instead), or null when it can. */
|
|
361
|
+
function unwaivableWhy(obId: string, unit: string): string | null {
|
|
362
|
+
if (obId === SIGNOFF_OBLIGATION) return `sign-off is given with --signoff, never waived (${signoffCommand(unit)})`;
|
|
363
|
+
if (obId === CREATE_OBLIGATION) return `an unaudited unit cannot be waived through — re-run \`sungen audit --screen ${unit}\``;
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** The review-queue line for a recorded waiver the board does not honour. */
|
|
368
|
+
const ignoredWaiverText = (obId: string, why: string, reason: string): string => `⚠️ waiver on ${obId} ignored: ${why}. Was: ${reason}`;
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Throws unless `obId` is an obligation of the board that a waiver can accept. Refused in every state,
|
|
372
|
+
* each with what does it instead: OB-signoff (before any audit it is not on the board, after one a
|
|
373
|
+
* waiver would let the human gate be waived away) and OB-create (an unaudited unit would pass every
|
|
374
|
+
* gate on a waiver; the audit measures it).
|
|
375
|
+
*/
|
|
376
|
+
export function assertWaivable(report: JourneyReport, unit: string, obId: string): void {
|
|
377
|
+
if (obId === SIGNOFF_OBLIGATION) {
|
|
378
|
+
throw new Error(`${SIGNOFF_OBLIGATION} is the human sign-off — it is given, not waived. Once every other obligation is satisfied or waived, a human runs: ${signoffCommand(unit)}`);
|
|
379
|
+
}
|
|
380
|
+
if (obId === CREATE_OBLIGATION) {
|
|
381
|
+
throw new Error(`${CREATE_OBLIGATION} means the unit has no audit that counts — an unaudited unit cannot be waived through. Run: sungen audit --screen ${unit}`);
|
|
382
|
+
}
|
|
383
|
+
const valid = report.obligations.map((o) => o.id).filter((id) => id !== SIGNOFF_OBLIGATION && id !== CREATE_OBLIGATION);
|
|
384
|
+
if (!valid.includes(obId)) {
|
|
385
|
+
throw new Error(`Unknown obligation "${obId}". Valid: ${valid.join(', ') || `none — the unit has no audit that counts; run sungen audit --screen ${unit}`}`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
276
389
|
/**
|
|
277
390
|
* Waive an obligation — REQUIRES a reason (anti-amnesia: a waiver leaves a recorded "why").
|
|
278
391
|
* Records the current evidence cursor so reconcile can invalidate it if the audit changes.
|
|
@@ -283,10 +396,7 @@ export function waive(projectRoot: string, unit: string, obId: string, reason: s
|
|
|
283
396
|
}
|
|
284
397
|
const slug = reportSlug(unit);
|
|
285
398
|
const fresh = runJourney(projectRoot, unit); // stops on a pending migration; imports legacy waivers first
|
|
286
|
-
|
|
287
|
-
if (!valid.includes(obId)) {
|
|
288
|
-
throw new Error(`Unknown obligation "${obId}". Valid: ${valid.join(', ')}`);
|
|
289
|
-
}
|
|
399
|
+
assertWaivable(fresh, unit, obId);
|
|
290
400
|
const given = { reason: reason.trim(), at: new Date().toISOString(), auditHashAtWaive: auditHashOf(projectRoot, slug) };
|
|
291
401
|
recordWaiver(projectRoot, unit, obId, given); // DOS-13.5: the decision layer is the record …
|
|
292
402
|
return runJourney(projectRoot, unit); // … and runJourney dual-writes it to the legacy file
|
|
@@ -297,8 +407,8 @@ export function waive(projectRoot: string, unit: string, obId: string, reason: s
|
|
|
297
407
|
* obligation is satisfied or waived (you cannot sign off an unclean suite). Records who/when +
|
|
298
408
|
* the evidence cursor; reconcile invalidates it (stale → re-review) if the suite later changes.
|
|
299
409
|
*/
|
|
300
|
-
|
|
301
|
-
|
|
410
|
+
/** Throws unless the board may be signed off: no decision problem, every other obligation accepted. */
|
|
411
|
+
export function assertCanSignOff(before: JourneyReport): void {
|
|
302
412
|
const blockers = before.obligations.filter((o) => o.id !== 'OB-signoff' && !accepted(o.status));
|
|
303
413
|
if (before.decisionFindings?.length) {
|
|
304
414
|
throw new Error(`Cannot sign off — ${before.decisionFindings.length} decision problem(s): ${before.decisionFindings.map((f) => `${f.rule} ${f.path}`).join(', ')}.`);
|
|
@@ -306,10 +416,26 @@ export function signoff(projectRoot: string, unit: string, by?: string): Journey
|
|
|
306
416
|
if (blockers.length) {
|
|
307
417
|
throw new Error(`Cannot sign off — still open: ${blockers.map((b) => b.id).join(', ')}. Fix, or waive each with a reason, first.`);
|
|
308
418
|
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/** Who signs off: `--by`, else the git user; throws when neither names anyone (never `by: unknown`). */
|
|
422
|
+
export function signoffSigner(projectRoot: string, unit: string, by?: string): { signer: string; gitUser: string } {
|
|
423
|
+
const gitUser = gitUserName(projectRoot);
|
|
424
|
+
const signer = by?.trim() || gitUser;
|
|
425
|
+
if (signer === 'unknown') {
|
|
426
|
+
throw new Error(`Cannot sign off — no git user.name is set and no --by was given, so the sign-off would name nobody. Say who signs: ${signoffCommand(unit)}`);
|
|
427
|
+
}
|
|
428
|
+
return { signer, gitUser };
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export function signoff(projectRoot: string, unit: string, by?: string): JourneyReport {
|
|
432
|
+
// Attribution is not self-declared (review item 4): the git user is always on the record.
|
|
433
|
+
const { signer, gitUser } = signoffSigner(projectRoot, unit, by);
|
|
434
|
+
assertCanSignOff(runJourney(projectRoot, unit));
|
|
309
435
|
const slug = reportSlug(unit);
|
|
310
436
|
const sp = statePath(projectRoot, slug);
|
|
311
437
|
const state = loadState(sp) || { unit, auditHash: '', waivers: {} };
|
|
312
|
-
state.signoff = { by, at: new Date().toISOString(), auditHashAtSignoff: auditHashOf(projectRoot, slug) };
|
|
438
|
+
state.signoff = { by: signer, ...(signer !== gitUser ? { recordedBy: gitUser } : {}), at: new Date().toISOString(), auditHashAtSignoff: auditHashOf(projectRoot, slug) };
|
|
313
439
|
saveState(sp, state);
|
|
314
440
|
return runJourney(projectRoot, unit);
|
|
315
441
|
}
|
|
@@ -333,11 +459,31 @@ export function writeJourneyFileBestEffort(file: string, text: string): string |
|
|
|
333
459
|
|
|
334
460
|
const ICON: Record<ObStatus, string> = { satisfied: '✅', 'needs-work': '⚠️ ', pending: '⏳', waived: '🚫', overridden: '🔓' };
|
|
335
461
|
|
|
462
|
+
/** `ms` as a person reads a duration: `850 ms`, `45 s`, `8 min 40 s`. */
|
|
463
|
+
function elapsedLabel(ms: number): string {
|
|
464
|
+
if (ms < 1000) return `${ms} ms`;
|
|
465
|
+
const s = Math.round(ms / 1000);
|
|
466
|
+
return s < 60 ? `${s} s` : `${Math.floor(s / 60)} min${s % 60 ? ` ${s % 60} s` : ''}`;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* A ledger step as the board shows it (#939 item 8). A step recorded with its elapsed milliseconds in the
|
|
471
|
+
* NAME (`--step "viewpoint 45000"`, the time meant for `--ms`) printed as a bare number; it reads
|
|
472
|
+
* `viewpoint (45 s)` now, and `timed` says the line needs its legend.
|
|
473
|
+
*/
|
|
474
|
+
function phaseLabel(step: string): { label: string; timed: boolean } {
|
|
475
|
+
const m = step.match(/^(.*\S)\s+(\d+)$/);
|
|
476
|
+
return m ? { label: `${m[1]} (${elapsedLabel(Number(m[2]))})`, timed: true } : { label: step, timed: false };
|
|
477
|
+
}
|
|
478
|
+
|
|
336
479
|
export function renderJourneyBoard(r: JourneyReport): string {
|
|
337
480
|
const L: string[] = [];
|
|
338
481
|
L.push(`# Journey — ${r.unit}`);
|
|
339
482
|
L.push('');
|
|
340
|
-
|
|
483
|
+
const phases = r.phasesDone.map(phaseLabel);
|
|
484
|
+
const legend = phases.some((p) => p.timed) || phaseLabel(r.youAreHere).timed
|
|
485
|
+
? ` — a time is that phase's elapsed time, from the ledger (\`sungen ledger report --screen ${r.unit}\`)` : '';
|
|
486
|
+
L.push(`📍 **You are here:** ${phaseLabel(r.youAreHere).label}` + (phases.length ? ` (phases: ${phases.map((p) => p.label).join(' → ')})${legend}` : ''));
|
|
341
487
|
if (r.gateStatus) L.push(` gate: **${r.gateStatus}** · score: ${r.score ?? '—'}/10`);
|
|
342
488
|
L.push('');
|
|
343
489
|
L.push('## What must still be true');
|
|
@@ -432,7 +578,14 @@ export function renderGate(v: GateVerdict): string {
|
|
|
432
578
|
if (v.blockers.length) {
|
|
433
579
|
L.push(v.phaseOverriddenBy ? ' Open obligations:' : ' Blocking obligations (must be satisfied or explicitly waived):');
|
|
434
580
|
for (const b of v.blockers) L.push(` • ${b.id} ${b.title} — ${b.detail}`);
|
|
435
|
-
|
|
581
|
+
// Name what each kind of blocker takes (#936): never offer to waive the sign-off or "never audited".
|
|
582
|
+
const waivable = v.blockers.map((b) => b.id).filter((id) => id.startsWith('OB-') && id !== SIGNOFF_OBLIGATION && id !== 'OB-create');
|
|
583
|
+
if (waivable.length) {
|
|
584
|
+
L.push(` → Self-correct (repair / run-test), or, if accepted, waive with a reason: \`sungen journey --screen ${v.unit} --waive ${waivable.length === 1 ? waivable[0] : `<${waivable.join('|')}>`} --reason "..."\`.`);
|
|
585
|
+
}
|
|
586
|
+
if (v.blockers.some((b) => b.id === SIGNOFF_OBLIGATION)) {
|
|
587
|
+
L.push(` → Human sign-off: once everything else is satisfied or waived, a human runs \`${signoffCommand(v.unit)}\`.`);
|
|
588
|
+
}
|
|
436
589
|
} else {
|
|
437
590
|
L.push(' All required obligations satisfied' + (v.waivedCredit.length ? ` (${v.waivedCredit.length} accepted via waiver)` : '') + '.');
|
|
438
591
|
}
|
package/src/harness/next-step.ts
CHANGED
|
@@ -19,7 +19,9 @@ import * as path from 'path';
|
|
|
19
19
|
import { reportSlug } from './unit-paths';
|
|
20
20
|
import { checkAssetsDrift } from '../orchestrator/assets-drift';
|
|
21
21
|
import type { HeadSnapshot } from '../contract/contract-git';
|
|
22
|
-
import {
|
|
22
|
+
import { boardHumanActions } from '../graph/board-human-actions';
|
|
23
|
+
import { evaluateGraph, evaluateHumanActions, EvaluateOptions } from '../graph/evaluate-graph';
|
|
24
|
+
import type { HumanAction } from '../graph/human-action';
|
|
23
25
|
import { CORE_NODES } from '../graph/nodes/core-node-catalog';
|
|
24
26
|
import type { TestCaseState } from '../graph/test-case-state-shape';
|
|
25
27
|
import { testCaseStates } from '../graph/test-case-states';
|
|
@@ -27,6 +29,7 @@ import {
|
|
|
27
29
|
countFailedResults, countScenarios, hasDeliverableFor, hasResolvedSelector, isCompiled, readIfExists, unitResultFiles,
|
|
28
30
|
} from '../graph/unit-artifact-probes';
|
|
29
31
|
import { AuditNotCounted, ContractFacts, ContractSummary, readContractFacts, readTrustedAudit } from '../graph/unit-state-facts';
|
|
32
|
+
import { ResultsFreshness, resultsFreshness } from '../graph/unit-results-freshness';
|
|
30
33
|
|
|
31
34
|
export interface NextStep {
|
|
32
35
|
/** The command to run, as the operator would type it. */
|
|
@@ -89,7 +92,22 @@ export interface UnitState {
|
|
|
89
92
|
contract?: ContractSummary;
|
|
90
93
|
/** Per-test-case state (DOS-11.1); absent when not read from disk or unreadable. */
|
|
91
94
|
testCases?: TestCaseState[];
|
|
95
|
+
/** Which run the results come from and what they are older than (#942); absent without results. Advisory, decides no step. */
|
|
96
|
+
results?: ResultsFreshness;
|
|
92
97
|
steps: NextStep[];
|
|
98
|
+
/**
|
|
99
|
+
* Every pending HUMAN-ONLY action ("Waiting for you", #954): the frontier's decisions (deriveHumanActions)
|
|
100
|
+
* and the board's asks (sign-off, a waiver to give again, a decision recorded during the run). Set by
|
|
101
|
+
* readUnitState; commands canonical (`sungen …`). An agent presents them and never runs them.
|
|
102
|
+
*/
|
|
103
|
+
humanActions?: HumanAction[];
|
|
104
|
+
/**
|
|
105
|
+
* The title of the blocking step that cut the frontier: past it only actions that do not depend on it
|
|
106
|
+
* are listed (sign-off, accept-in-run and the first contract approval wait). Absent when nothing cuts.
|
|
107
|
+
*/
|
|
108
|
+
humanActionsHeldBy?: string;
|
|
109
|
+
/** The canonical command of that blocking step (#966) — what clears the hold. */
|
|
110
|
+
humanActionsHeldByCommand?: string;
|
|
93
111
|
}
|
|
94
112
|
|
|
95
113
|
export interface ReadUnitStateOptions {
|
|
@@ -162,7 +180,17 @@ export function readUnitState(
|
|
|
162
180
|
state.testCases = testCaseStates(projectRoot, unit, { unitDir, auditFindings: state.audit?.findings ?? [] })
|
|
163
181
|
.map((tc) => (contract?.cases.has(tc.id) ? { ...tc, contract: contract.cases.get(tc.id) } : tc));
|
|
164
182
|
} catch { /* an unparseable .feature — the unit-level nodes still answer, as before test-case state */ }
|
|
183
|
+
try {
|
|
184
|
+
const results = resultsFreshness(projectRoot, unitDir, unit);
|
|
185
|
+
if (results) state.results = results;
|
|
186
|
+
} catch { /* advisory: results that cannot be dated add nothing */ }
|
|
165
187
|
state.steps = deriveSteps(state);
|
|
188
|
+
const graph = evaluateHumanActions(CORE_NODES, state, state.testCases);
|
|
189
|
+
const seen = new Set<string>();
|
|
190
|
+
state.humanActions = [...graph.actions, ...boardHumanActions(projectRoot, unitDir, state, graph.cut)]
|
|
191
|
+
.filter((a) => !seen.has(a.command) && Boolean(seen.add(a.command)));
|
|
192
|
+
if (graph.heldBy) state.humanActionsHeldBy = graph.heldBy;
|
|
193
|
+
if (graph.heldByCommand) state.humanActionsHeldByCommand = graph.heldByCommand;
|
|
166
194
|
return state;
|
|
167
195
|
}
|
|
168
196
|
|
|
@@ -174,3 +202,8 @@ export function readUnitState(
|
|
|
174
202
|
export function deriveSteps(s: UnitState, opts: EvaluateOptions = {}): NextStep[] {
|
|
175
203
|
return evaluateGraph(CORE_NODES, s, s.testCases, opts);
|
|
176
204
|
}
|
|
205
|
+
|
|
206
|
+
/** The human-only actions of the same frontier (#954) — the graph's part of `humanActions`, without the board. */
|
|
207
|
+
export function deriveHumanActions(s: UnitState): HumanAction[] {
|
|
208
|
+
return evaluateHumanActions(CORE_NODES, s, s.testCases).actions;
|
|
209
|
+
}
|