@deftai/directive-core 0.103.0 → 0.105.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authz/classify.js +682 -35
- package/dist/check/cached-orchestrator.d.ts +1 -1
- package/dist/check/cached-orchestrator.js +2 -2
- package/dist/check/gate-lists.d.ts +3 -2
- package/dist/check/gate-lists.js +5 -2
- package/dist/check/index.d.ts +1 -0
- package/dist/check/index.js +1 -0
- package/dist/check/named-cause.d.ts +1 -1
- package/dist/check/named-cause.js +33 -3
- package/dist/check/session-completed-ac.d.ts +39 -0
- package/dist/check/session-completed-ac.js +198 -0
- package/dist/codebase/default-extractor.d.ts +5 -0
- package/dist/codebase/default-extractor.js +7 -15
- package/dist/deposit/copy-tree.js +60 -0
- package/dist/deposit/python-free.js +26 -18
- package/dist/doctor/checks.d.ts +8 -0
- package/dist/doctor/checks.js +37 -25
- package/dist/doctor/doctor-state.d.ts +4 -0
- package/dist/doctor/doctor-state.js +7 -1
- package/dist/doctor/main.js +9 -14
- package/dist/doctor/types.d.ts +9 -0
- package/dist/fs/contained-write.d.ts +115 -1
- package/dist/fs/contained-write.js +242 -5
- package/dist/fs/mutation-ledger.d.ts +54 -0
- package/dist/fs/mutation-ledger.js +133 -0
- package/dist/fs/non-product-dirs.d.ts +79 -0
- package/dist/fs/non-product-dirs.js +96 -0
- package/dist/hooks/dispatcher.d.ts +3 -1
- package/dist/hooks/dispatcher.js +14 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/init-deposit/agent-hooks.js +17 -15
- package/dist/init-deposit/core-guard-pin-content.d.ts +7 -0
- package/dist/init-deposit/core-guard-pin-content.js +393 -0
- package/dist/init-deposit/gitignore.js +9 -0
- package/dist/init-deposit/hygiene.d.ts +47 -11
- package/dist/init-deposit/hygiene.js +172 -37
- package/dist/init-deposit/legacy-detect.js +6 -2
- package/dist/init-deposit/refresh.d.ts +21 -1
- package/dist/init-deposit/refresh.js +106 -9
- package/dist/init-deposit/scaffold.js +28 -279
- package/dist/init-deposit/slash-deposit.js +13 -7
- package/dist/init-deposit/xbrief-projections.d.ts +8 -1
- package/dist/init-deposit/xbrief-projections.js +24 -11
- package/dist/intake/clause-derivation.d.ts +74 -0
- package/dist/intake/clause-derivation.js +409 -0
- package/dist/intake/index.d.ts +1 -0
- package/dist/intake/index.js +1 -0
- package/dist/intake/issue-ingest.d.ts +3 -1
- package/dist/intake/issue-ingest.js +36 -43
- package/dist/lifecycle/completed-tracked-on-delivery.d.ts +5 -0
- package/dist/lifecycle/completed-tracked-on-delivery.js +85 -18
- package/dist/literal-acceptance/capture.d.ts +10 -0
- package/dist/literal-acceptance/capture.js +224 -75
- package/dist/literal-acceptance/evaluate.d.ts +29 -4
- package/dist/literal-acceptance/evaluate.js +70 -2
- package/dist/literal-acceptance/index.d.ts +3 -3
- package/dist/literal-acceptance/index.js +3 -3
- package/dist/literal-acceptance/safety.d.ts +41 -0
- package/dist/literal-acceptance/safety.js +126 -2
- package/dist/literal-acceptance/types.d.ts +8 -0
- package/dist/orphan-active/evaluate.d.ts +7 -1
- package/dist/orphan-active/evaluate.js +102 -23
- package/dist/platform/platform-capabilities.js +2 -3
- package/dist/policy/base-branch.d.ts +37 -0
- package/dist/policy/base-branch.js +114 -0
- package/dist/policy/branch-sync.d.ts +80 -0
- package/dist/policy/branch-sync.js +211 -0
- package/dist/policy/ceremony-dial-escalation.js +1 -0
- package/dist/policy/ceremony-dial.js +4 -14
- package/dist/policy/delivery-branch.d.ts +5 -3
- package/dist/policy/delivery-branch.js +7 -3
- package/dist/policy/file-size-thresholds.d.ts +24 -0
- package/dist/policy/file-size-thresholds.js +23 -0
- package/dist/policy/forge-outage-retry.d.ts +47 -0
- package/dist/policy/forge-outage-retry.js +157 -0
- package/dist/policy/index.d.ts +7 -0
- package/dist/policy/index.js +66 -7
- package/dist/policy/project-invariants.d.ts +50 -0
- package/dist/policy/project-invariants.js +205 -0
- package/dist/policy/sync-default.d.ts +129 -0
- package/dist/policy/sync-default.js +452 -0
- package/dist/policy/sync-max-files.d.ts +93 -0
- package/dist/policy/sync-max-files.js +192 -0
- package/dist/pr-monitor/absent-required.d.ts +11 -0
- package/dist/pr-monitor/absent-required.js +61 -0
- package/dist/pr-monitor/constants.d.ts +7 -0
- package/dist/pr-monitor/constants.js +7 -0
- package/dist/pr-monitor/index.d.ts +2 -1
- package/dist/pr-monitor/index.js +2 -1
- package/dist/pr-monitor/main.js +4 -0
- package/dist/pr-monitor/monitor.js +24 -1
- package/dist/pr-wait-mergeable/classify.js +6 -0
- package/dist/preflight/evaluate.d.ts +9 -0
- package/dist/preflight/evaluate.js +49 -1
- package/dist/preflight/index.d.ts +5 -1
- package/dist/preflight/index.js +2 -0
- package/dist/preflight/intended-placement.d.ts +62 -0
- package/dist/preflight/intended-placement.js +275 -0
- package/dist/preflight/project-invariants-gate.d.ts +29 -0
- package/dist/preflight/project-invariants-gate.js +82 -0
- package/dist/product-first-done-gate/acceptance-resolver.d.ts +128 -0
- package/dist/product-first-done-gate/acceptance-resolver.js +144 -0
- package/dist/product-first-done-gate/acceptance.d.ts +7 -4
- package/dist/product-first-done-gate/acceptance.js +79 -8
- package/dist/product-first-done-gate/empty-resolution.d.ts +5 -2
- package/dist/product-first-done-gate/empty-resolution.js +13 -2
- package/dist/product-first-done-gate/evaluate.d.ts +29 -0
- package/dist/product-first-done-gate/evaluate.js +307 -33
- package/dist/product-first-done-gate/index.d.ts +3 -1
- package/dist/product-first-done-gate/index.js +2 -1
- package/dist/release/build-dist.d.ts +101 -0
- package/dist/release/build-dist.js +428 -46
- package/dist/run-summary/emit.d.ts +50 -4
- package/dist/run-summary/emit.js +262 -26
- package/dist/run-summary/index.d.ts +3 -2
- package/dist/run-summary/index.js +2 -1
- package/dist/run-summary/session-id.d.ts +21 -0
- package/dist/run-summary/session-id.js +92 -0
- package/dist/run-summary/share.d.ts +1 -1
- package/dist/run-summary/share.js +2 -2
- package/dist/run-summary/types.d.ts +38 -6
- package/dist/run-summary/types.js +1 -0
- package/dist/scope/acceptance-activate-gate.d.ts +1 -1
- package/dist/scope/acceptance-activate-gate.js +4 -4
- package/dist/scope/acceptance-evidence.d.ts +27 -0
- package/dist/scope/acceptance-evidence.js +66 -0
- package/dist/scope/capacity-stamp.d.ts +2 -0
- package/dist/scope/capacity-stamp.js +4 -0
- package/dist/scope/delivery-evidence.d.ts +9 -3
- package/dist/scope/delivery-evidence.js +39 -27
- package/dist/scope/index.d.ts +1 -0
- package/dist/scope/index.js +1 -0
- package/dist/scope/parent-lineage.js +7 -6
- package/dist/scope/project-invariant-coverage.d.ts +54 -0
- package/dist/scope/project-invariant-coverage.js +322 -0
- package/dist/scope/transition.js +66 -4
- package/dist/scope-provenance/compare-intent.d.ts +27 -0
- package/dist/scope-provenance/compare-intent.js +233 -0
- package/dist/scope-provenance/digest.d.ts +18 -1
- package/dist/scope-provenance/digest.js +16 -11
- package/dist/scope-provenance/evaluate.d.ts +5 -1
- package/dist/scope-provenance/evaluate.js +64 -4
- package/dist/scope-provenance/extract-intent.d.ts +43 -0
- package/dist/scope-provenance/extract-intent.js +262 -0
- package/dist/scope-provenance/index.d.ts +7 -1
- package/dist/scope-provenance/index.js +7 -1
- package/dist/scope-provenance/intent-digest.d.ts +12 -0
- package/dist/scope-provenance/intent-digest.js +33 -0
- package/dist/scope-provenance/intent-evaluate.d.ts +43 -0
- package/dist/scope-provenance/intent-evaluate.js +149 -0
- package/dist/scope-provenance/json-tokenizer.d.ts +19 -0
- package/dist/scope-provenance/json-tokenizer.js +225 -0
- package/dist/scope-provenance/known-machine.d.ts +38 -0
- package/dist/scope-provenance/known-machine.js +157 -0
- package/dist/scope-provenance/mint-artifacts.d.ts +102 -0
- package/dist/scope-provenance/mint-artifacts.js +425 -0
- package/dist/session/ac-pass-banking.d.ts +4 -36
- package/dist/session/ac-pass-banking.js +30 -77
- package/dist/session/ac-pass-reuse.d.ts +53 -0
- package/dist/session/ac-pass-reuse.js +95 -0
- package/dist/session/ceremony-dial-evidence.d.ts +42 -0
- package/dist/session/ceremony-dial-evidence.js +194 -0
- package/dist/session/git.js +19 -4
- package/dist/session/index.d.ts +5 -0
- package/dist/session/index.js +5 -0
- package/dist/session/occupancy-stress-worker.d.ts +7 -0
- package/dist/session/occupancy-stress-worker.js +36 -0
- package/dist/session/occupancy.d.ts +97 -0
- package/dist/session/occupancy.js +422 -0
- package/dist/session/openclaw-soft-rebind-deposit.js +6 -6
- package/dist/session/product-state-hash.d.ts +26 -0
- package/dist/session/product-state-hash.js +683 -0
- package/dist/session/session-ready.d.ts +2 -0
- package/dist/session/session-ready.js +52 -30
- package/dist/session/session-start.d.ts +7 -0
- package/dist/session/session-start.js +88 -5
- package/dist/session/verify-ac-session-cache.d.ts +42 -0
- package/dist/session/verify-ac-session-cache.js +126 -0
- package/dist/session/verify-session-ritual.d.ts +5 -0
- package/dist/session/verify-session-ritual.js +9 -0
- package/dist/slash/openclaw-deposit.js +13 -9
- package/dist/slice/constants.d.ts +2 -2
- package/dist/slice/constants.js +2 -2
- package/dist/slice/existing.js +21 -4
- package/dist/slice/lock.d.ts +16 -1
- package/dist/slice/lock.js +137 -11
- package/dist/slice/record.d.ts +8 -5
- package/dist/slice/record.js +21 -13
- package/dist/story-ready/evaluate.d.ts +2 -0
- package/dist/story-ready/evaluate.js +13 -0
- package/dist/swarm/complete-cohort.d.ts +3 -0
- package/dist/swarm/complete-cohort.js +59 -11
- package/dist/swarm/finalize-cohort.js +33 -2
- package/dist/swarm/launch.d.ts +22 -0
- package/dist/swarm/launch.js +155 -0
- package/dist/swarm/routing-set-cli.d.ts +11 -1
- package/dist/swarm/routing-set-cli.js +13 -3
- package/dist/swarm/routing-verify.d.ts +12 -0
- package/dist/swarm/routing-verify.js +59 -20
- package/dist/swarm/routing.d.ts +14 -0
- package/dist/swarm/routing.js +53 -3
- package/dist/telemetry-coverage/evaluate.d.ts +42 -0
- package/dist/telemetry-coverage/evaluate.js +148 -0
- package/dist/telemetry-coverage/fake-trial.d.ts +45 -0
- package/dist/telemetry-coverage/fake-trial.js +204 -0
- package/dist/telemetry-coverage/index.d.ts +5 -0
- package/dist/telemetry-coverage/index.js +5 -0
- package/dist/telemetry-coverage/kinds.d.ts +19 -0
- package/dist/telemetry-coverage/kinds.js +54 -0
- package/dist/telemetry-coverage/scan-callers.d.ts +25 -0
- package/dist/telemetry-coverage/scan-callers.js +168 -0
- package/dist/triage/actions/index.js +4 -1
- package/dist/ts-check-lane/index.d.ts +3 -0
- package/dist/ts-check-lane/index.js +2 -0
- package/dist/ts-check-lane/progress-reporter.d.ts +17 -0
- package/dist/ts-check-lane/progress-reporter.js +38 -0
- package/dist/ts-check-lane/progress.d.ts +32 -0
- package/dist/ts-check-lane/progress.js +57 -0
- package/dist/ts-check-lane/run-lane.d.ts +3 -1
- package/dist/ts-check-lane/run-lane.js +8 -6
- package/dist/validate-content/validate-links.d.ts +8 -0
- package/dist/validate-content/validate-links.js +9 -15
- package/dist/value/feedback-file.js +12 -2
- package/dist/vbrief-build/index.d.ts +1 -0
- package/dist/vbrief-build/index.js +1 -0
- package/dist/vbrief-build/project-invariants-io.d.ts +16 -0
- package/dist/vbrief-build/project-invariants-io.js +41 -0
- package/dist/vbrief-reconcile/graph.js +18 -1
- package/dist/vbrief-reconcile/index.d.ts +3 -1
- package/dist/vbrief-reconcile/index.js +2 -1
- package/dist/vbrief-reconcile/origin-freshness.d.ts +44 -0
- package/dist/vbrief-reconcile/origin-freshness.js +221 -0
- package/dist/vbrief-reconcile/types.d.ts +13 -0
- package/dist/vbrief-reconcile/umbrellas.d.ts +14 -0
- package/dist/vbrief-reconcile/umbrellas.js +261 -26
- package/dist/vbrief-validate/index.d.ts +1 -1
- package/dist/vbrief-validate/index.js +1 -1
- package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
- package/dist/vbrief-validate/plan-hooks.js +68 -0
- package/dist/verify-ac/evaluate.d.ts +7 -1
- package/dist/verify-ac/evaluate.js +34 -26
- package/dist/verify-ac/flag.d.ts +4 -0
- package/dist/verify-ac/flag.js +16 -0
- package/dist/verify-ac/index.d.ts +2 -2
- package/dist/verify-ac/index.js +2 -2
- package/dist/verify-source/contained-writes.js +2 -0
- package/dist/verify-source/index.d.ts +1 -0
- package/dist/verify-source/index.js +1 -0
- package/dist/verify-source/verify-stubs.d.ts +15 -1
- package/dist/verify-source/verify-stubs.js +20 -16
- package/dist/vitest-runner/win32-coverage-tmp-setup.d.ts +5 -6
- package/dist/vitest-runner/win32-coverage-tmp-setup.js +5 -6
- package/dist/xbrief-migrate/migrate-project.js +2 -5
- package/package.json +3 -3
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
* Directive thin wrappers (`isThinWrapperMarkdown`). Consumer-customized content
|
|
14
14
|
* at a product filename is left untouched.
|
|
15
15
|
*/
|
|
16
|
-
import { existsSync,
|
|
16
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
17
17
|
import { basename, dirname, join, resolve } from "node:path";
|
|
18
18
|
import { assertDepositContained } from "../deposit/contain.js";
|
|
19
|
-
import { containedWrite } from "../fs/contained-write.js";
|
|
19
|
+
import { containedMkdir, containedRemove, containedRename, containedWrite, } from "../fs/contained-write.js";
|
|
20
20
|
import { isHostSlashCommandDepositEnabled, loadHostSlashCommandsPolicyFromProject, } from "../policy/host-slash-commands.js";
|
|
21
21
|
import { emitHostCommandFiles, getHostCommandLayout, isThinWrapperMarkdown, listSlashEmitterHosts, } from "../slash/index.js";
|
|
22
22
|
import { listProductCommands, logicalIdToFilename } from "../slash/product-set.js";
|
|
@@ -115,7 +115,7 @@ function writeHostCommandFileIfChanged(projectRoot, file) {
|
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
const parent = dirname(absolute);
|
|
118
|
-
|
|
118
|
+
containedMkdir({ root: projectRoot, target: parent });
|
|
119
119
|
// Atomic replace via temp under project root (#2951).
|
|
120
120
|
const tmpName = `${basename(absolute)}.deft-${process.pid}.tmp`;
|
|
121
121
|
const temporary = join(parent, tmpName);
|
|
@@ -126,11 +126,16 @@ function writeHostCommandFileIfChanged(projectRoot, file) {
|
|
|
126
126
|
data: file.contents,
|
|
127
127
|
mode: "replace",
|
|
128
128
|
});
|
|
129
|
-
|
|
129
|
+
containedRename({
|
|
130
|
+
root: projectRoot,
|
|
131
|
+
from: temporary,
|
|
132
|
+
to: absolute,
|
|
133
|
+
mutation: false,
|
|
134
|
+
});
|
|
130
135
|
}
|
|
131
136
|
catch (err) {
|
|
132
137
|
try {
|
|
133
|
-
|
|
138
|
+
containedRemove({ root: projectRoot, target: temporary, mutation: false });
|
|
134
139
|
}
|
|
135
140
|
catch {
|
|
136
141
|
/* best-effort cleanup */
|
|
@@ -162,8 +167,9 @@ function stripManagedHostCommandFiles(projectRoot, hostId) {
|
|
|
162
167
|
continue;
|
|
163
168
|
}
|
|
164
169
|
try {
|
|
165
|
-
|
|
166
|
-
|
|
170
|
+
if (containedRemove({ root: projectRoot, target: absolute }).removed) {
|
|
171
|
+
removed.push(file.relativePath);
|
|
172
|
+
}
|
|
167
173
|
}
|
|
168
174
|
catch {
|
|
169
175
|
/* best-effort */
|
|
@@ -7,11 +7,18 @@
|
|
|
7
7
|
*/
|
|
8
8
|
/** Rewrite legacy vbrief/.eval description paths for xbrief/schemas projection (#2670). */
|
|
9
9
|
export declare function rewriteProjectedSchemaContent(content: string): string;
|
|
10
|
+
/** Fail closed when planned (source-rewritten) schema bytes still cite vbrief/.eval/. */
|
|
11
|
+
export declare function assertPlannedSchemaDescriptionRooted(rel: string, text: string): void;
|
|
10
12
|
/**
|
|
11
13
|
* Fail closed when a projected xbrief/schemas file still cites vbrief/.eval/.
|
|
12
14
|
* Upstream vbrief/schemas/ may keep legacy paths; consumer copies must not (#2670).
|
|
15
|
+
*
|
|
16
|
+
* `skipRel` is dest files the collect record plans to replace (#3456 plan-note):
|
|
17
|
+
* never validate stale dest bytes a skipped write would have replaced.
|
|
13
18
|
*/
|
|
14
|
-
export declare function assertProjectedSchemaDescriptionsRooted(projectDir: string, destinationDir: string
|
|
19
|
+
export declare function assertProjectedSchemaDescriptionsRooted(projectDir: string, destinationDir: string, options?: {
|
|
20
|
+
readonly skipRel?: ReadonlySet<string>;
|
|
21
|
+
}): void;
|
|
15
22
|
/**
|
|
16
23
|
* Synchronize framework-owned xBRIEF schemas while preserving unknown consumer
|
|
17
24
|
* files. The obsolete v0.6 root schema is the only destination-only file this
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* freshness. A current `.deft/core/VERSION` therefore cannot short-circuit
|
|
6
6
|
* these repairs.
|
|
7
7
|
*/
|
|
8
|
-
import { existsSync,
|
|
8
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
9
9
|
import { join, relative, resolve } from "node:path";
|
|
10
|
-
import { containedWrite } from "../fs/contained-write.js";
|
|
10
|
+
import { containedMkdir, containedRemove, containedWrite } from "../fs/contained-write.js";
|
|
11
11
|
import { assertDestinationNotSymlink } from "../fs/projection-containment.js";
|
|
12
12
|
import { resolveLifecycleRoot } from "../layout/resolve.js";
|
|
13
13
|
import { DEV_FALLBACK } from "../platform/constants.js";
|
|
@@ -50,20 +50,30 @@ export function rewriteProjectedSchemaContent(content) {
|
|
|
50
50
|
? content.replaceAll(LEGACY_EVAL_PATH_PREFIX, XBRIEF_EVAL_PATH_PREFIX)
|
|
51
51
|
: content;
|
|
52
52
|
}
|
|
53
|
+
/** Fail closed when planned (source-rewritten) schema bytes still cite vbrief/.eval/. */
|
|
54
|
+
export function assertPlannedSchemaDescriptionRooted(rel, text) {
|
|
55
|
+
if (text.includes(LEGACY_EVAL_PATH_PREFIX)) {
|
|
56
|
+
throw new Error(`projected xbrief schema still cites ${LEGACY_EVAL_PATH_PREFIX}: ${join(MIGRATED_ARTIFACT_DIR, "schemas", rel)}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
53
59
|
/**
|
|
54
60
|
* Fail closed when a projected xbrief/schemas file still cites vbrief/.eval/.
|
|
55
61
|
* Upstream vbrief/schemas/ may keep legacy paths; consumer copies must not (#2670).
|
|
62
|
+
*
|
|
63
|
+
* `skipRel` is dest files the collect record plans to replace (#3456 plan-note):
|
|
64
|
+
* never validate stale dest bytes a skipped write would have replaced.
|
|
56
65
|
*/
|
|
57
|
-
export function assertProjectedSchemaDescriptionsRooted(projectDir, destinationDir) {
|
|
66
|
+
export function assertProjectedSchemaDescriptionsRooted(projectDir, destinationDir, options = {}) {
|
|
58
67
|
assertDestinationNotSymlink(projectDir, destinationDir);
|
|
59
68
|
if (!isDirectory(destinationDir))
|
|
60
69
|
return;
|
|
70
|
+
const skipRel = options.skipRel;
|
|
61
71
|
for (const rel of collectSchemaFiles(destinationDir)) {
|
|
72
|
+
if (skipRel?.has(rel))
|
|
73
|
+
continue;
|
|
62
74
|
const full = join(destinationDir, rel);
|
|
63
75
|
const text = readFileSync(full, "utf8");
|
|
64
|
-
|
|
65
|
-
throw new Error(`projected xbrief schema still cites ${LEGACY_EVAL_PATH_PREFIX}: ${join(MIGRATED_ARTIFACT_DIR, "schemas", rel)}`);
|
|
66
|
-
}
|
|
76
|
+
assertPlannedSchemaDescriptionRooted(rel, text);
|
|
67
77
|
}
|
|
68
78
|
}
|
|
69
79
|
function writeFileIfChanged(projectDir, target, content) {
|
|
@@ -99,23 +109,26 @@ export function syncConsumerXbriefSchemas(projectDir, deftDir) {
|
|
|
99
109
|
}
|
|
100
110
|
const destinationDir = join(projectDir, MIGRATED_ARTIFACT_DIR, "schemas");
|
|
101
111
|
assertDestinationNotSymlink(projectDir, destinationDir);
|
|
102
|
-
|
|
112
|
+
containedMkdir({ root: resolve(projectDir), target: destinationDir });
|
|
103
113
|
let changed = false;
|
|
114
|
+
const plannedRel = new Set();
|
|
104
115
|
for (const rel of collectSchemaFiles(sourceDir)) {
|
|
105
116
|
if (rel === OBSOLETE_CORE_SCHEMA)
|
|
106
117
|
continue;
|
|
107
118
|
const source = join(sourceDir, rel);
|
|
108
119
|
const destination = join(destinationDir, rel);
|
|
109
120
|
const projected = rewriteProjectedSchemaContent(readFileSync(source, "utf8"));
|
|
121
|
+
// Planned content is the oracle. Dest bytes a skipped write would replace
|
|
122
|
+
// are not validated (#3456 plan-note).
|
|
123
|
+
assertPlannedSchemaDescriptionRooted(rel, projected);
|
|
124
|
+
plannedRel.add(rel);
|
|
110
125
|
changed = writeFileIfChanged(projectDir, destination, projected) || changed;
|
|
111
126
|
}
|
|
112
127
|
const obsoleteDestination = join(destinationDir, OBSOLETE_CORE_SCHEMA);
|
|
113
|
-
|
|
114
|
-
if (existsSync(obsoleteDestination)) {
|
|
115
|
-
rmSync(obsoleteDestination, { force: true });
|
|
128
|
+
if (containedRemove({ root: projectDir, target: obsoleteDestination }).removed) {
|
|
116
129
|
changed = true;
|
|
117
130
|
}
|
|
118
|
-
assertProjectedSchemaDescriptionsRooted(projectDir, destinationDir);
|
|
131
|
+
assertProjectedSchemaDescriptionsRooted(projectDir, destinationDir, { skipRel: plannedRel });
|
|
119
132
|
return changed;
|
|
120
133
|
}
|
|
121
134
|
function syncBareVersionMarkerWithPolicy(projectDir, version, allowRootFallback) {
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-authored briefs run #3323 clause derivation on activate/promote (#3360).
|
|
3
|
+
*
|
|
4
|
+
* issue:ingest already stamps clauses. The dominant consumer path authors the
|
|
5
|
+
* brief by hand; #3334 only required plan.acceptance to exist. This module is
|
|
6
|
+
* the missing derive step.
|
|
7
|
+
*/
|
|
8
|
+
import { type AcceptanceClause } from "../verify-ac/clauses.js";
|
|
9
|
+
/** One-line remediation when a stamp has no statement-traceable clause (#3398). */
|
|
10
|
+
export declare const CLAUSE_STAMP_IMPLEMENTATION_ONLY_REMEDIATION = "derive clauses from the statement's testable constraints";
|
|
11
|
+
export type ClauseProvenance = "statement" | "implementation";
|
|
12
|
+
export interface ProvenanceCounts {
|
|
13
|
+
readonly statement: number;
|
|
14
|
+
readonly implementation: number;
|
|
15
|
+
}
|
|
16
|
+
export interface TracedAcceptanceClause extends AcceptanceClause {
|
|
17
|
+
readonly provenance: ClauseProvenance;
|
|
18
|
+
}
|
|
19
|
+
export interface ClauseStampPreparation {
|
|
20
|
+
readonly ok: boolean;
|
|
21
|
+
readonly clauses: readonly TracedAcceptanceClause[];
|
|
22
|
+
readonly provenance_counts: ProvenanceCounts;
|
|
23
|
+
readonly ambiguity_attestation?: "none_found";
|
|
24
|
+
readonly remediation?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface AmbiguityAttestationCheck {
|
|
27
|
+
readonly ok: boolean;
|
|
28
|
+
readonly attested: boolean;
|
|
29
|
+
readonly kind: "ambiguous-clause" | "none_found" | "missing";
|
|
30
|
+
readonly message: string;
|
|
31
|
+
}
|
|
32
|
+
/** True when activate/promote must run #3323 (absent, empty none_stated, or command-only). */
|
|
33
|
+
export declare function needsClauseDerivation(acceptance: unknown): boolean;
|
|
34
|
+
/** Task-statement text from title, narratives, and item Acceptance fields. */
|
|
35
|
+
export declare function collectTaskStatementFromPlan(plan: Record<string, unknown>): string;
|
|
36
|
+
export interface ClauseDerivationResult {
|
|
37
|
+
readonly applied: boolean;
|
|
38
|
+
readonly clauses: readonly (AcceptanceClause | TracedAcceptanceClause)[];
|
|
39
|
+
readonly notice: string;
|
|
40
|
+
}
|
|
41
|
+
/** Statement-traceable when the clause text or its identifiers appear in the statement. */
|
|
42
|
+
export declare function traceClauseProvenance(clause: AcceptanceClause, statement: string): ClauseProvenance;
|
|
43
|
+
export declare function countClauseProvenance(clauses: readonly TracedAcceptanceClause[]): ProvenanceCounts;
|
|
44
|
+
/** Fail closed when every clause is implementation-provenance; else attest ambiguity. */
|
|
45
|
+
export declare function prepareClauseStamp(clauses: readonly AcceptanceClause[], statement: string): ClauseStampPreparation;
|
|
46
|
+
/** Config-error surface for verify:ac: attestation or an ambiguous clause with readings. */
|
|
47
|
+
export declare function evaluateAmbiguityAttestation(acceptance: unknown): AmbiguityAttestationCheck;
|
|
48
|
+
/**
|
|
49
|
+
* Annotate stamped clauses with provenance and an ambiguity attestation.
|
|
50
|
+
* An all-implementation stamp is stripped (never sufficient alone).
|
|
51
|
+
*/
|
|
52
|
+
export declare function applyClauseQualityToPlan(plan: Record<string, unknown>): ClauseDerivationResult;
|
|
53
|
+
/** Ingest path: strip an implementation-only stamp and record the remediation. */
|
|
54
|
+
export declare function applyClauseQualityForIngest(plan: Record<string, unknown>): ClauseDerivationResult;
|
|
55
|
+
/**
|
|
56
|
+
* Stamp derived clauses onto plan.acceptance in place. Headless: records
|
|
57
|
+
* chosen_reading, never blocks on a question.
|
|
58
|
+
*/
|
|
59
|
+
export declare function applyClauseDerivationToPlan(plan: Record<string, unknown>, options?: {
|
|
60
|
+
readonly projectRoot?: string;
|
|
61
|
+
readonly emitStamp?: boolean;
|
|
62
|
+
}): ClauseDerivationResult;
|
|
63
|
+
/** Stable fingerprint of plan.acceptance for first-write / material-change detection (#3355). */
|
|
64
|
+
export declare function acceptanceFingerprint(acceptance: unknown): string | null;
|
|
65
|
+
/** True when next is a first write or a material change versus previous (#3355). */
|
|
66
|
+
export declare function isMaterialAcceptanceChange(previous: unknown, next: unknown): boolean;
|
|
67
|
+
/** Fail-open acceptance_stamp emission (same contract as issue:ingest / #3355). */
|
|
68
|
+
export declare function emitAcceptanceStampFromPlan(projectRoot: string, plan: unknown, env?: NodeJS.ProcessEnv): void;
|
|
69
|
+
/**
|
|
70
|
+
* Emit acceptance_stamp when plan.acceptance is first written or materially changed.
|
|
71
|
+
* State-observed: callers pass the before/after blocks from any lifecycle verb (#3355).
|
|
72
|
+
*/
|
|
73
|
+
export declare function maybeEmitAcceptanceStampFromChange(projectRoot: string, previous: unknown, next: unknown, env?: NodeJS.ProcessEnv): boolean;
|
|
74
|
+
//# sourceMappingURL=clause-derivation.d.ts.map
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-authored briefs run #3323 clause derivation on activate/promote (#3360).
|
|
3
|
+
*
|
|
4
|
+
* issue:ingest already stamps clauses. The dominant consumer path authors the
|
|
5
|
+
* brief by hand; #3334 only required plan.acceptance to exist. This module is
|
|
6
|
+
* the missing derive step.
|
|
7
|
+
*/
|
|
8
|
+
import { ENV_RUN_SUMMARY_PATH, RunSummaryEmitter, } from "../run-summary/index.js";
|
|
9
|
+
import { deriveAcceptanceClauses, readAcceptanceClauses, serializeAcceptanceClauses, } from "../verify-ac/clauses.js";
|
|
10
|
+
/** One-line remediation when a stamp has no statement-traceable clause (#3398). */
|
|
11
|
+
export const CLAUSE_STAMP_IMPLEMENTATION_ONLY_REMEDIATION = "derive clauses from the statement's testable constraints";
|
|
12
|
+
const NARRATIVE_KEYS = [
|
|
13
|
+
"Overview",
|
|
14
|
+
"Description",
|
|
15
|
+
"Acceptance",
|
|
16
|
+
"AcceptanceCriteria",
|
|
17
|
+
"Acceptance sketch",
|
|
18
|
+
"AcceptanceSketch",
|
|
19
|
+
"Test",
|
|
20
|
+
"Verification",
|
|
21
|
+
"ImplementationPlan",
|
|
22
|
+
];
|
|
23
|
+
function asRecord(value) {
|
|
24
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
function isNonEmptyString(value) {
|
|
30
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
31
|
+
}
|
|
32
|
+
/** True when activate/promote must run #3323 (absent, empty none_stated, or command-only). */
|
|
33
|
+
export function needsClauseDerivation(acceptance) {
|
|
34
|
+
if (acceptance === undefined || acceptance === null) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
const rec = asRecord(acceptance);
|
|
38
|
+
if (rec === null) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
return !(Array.isArray(rec.clauses) && rec.clauses.length > 0);
|
|
42
|
+
}
|
|
43
|
+
/** Task-statement text from title, narratives, and item Acceptance fields. */
|
|
44
|
+
export function collectTaskStatementFromPlan(plan) {
|
|
45
|
+
const parts = [];
|
|
46
|
+
if (isNonEmptyString(plan.title)) {
|
|
47
|
+
parts.push(plan.title.trim());
|
|
48
|
+
}
|
|
49
|
+
const narratives = asRecord(plan.narratives);
|
|
50
|
+
if (narratives !== null) {
|
|
51
|
+
const seen = new Set();
|
|
52
|
+
for (const key of NARRATIVE_KEYS) {
|
|
53
|
+
const value = narratives[key];
|
|
54
|
+
if (isNonEmptyString(value)) {
|
|
55
|
+
parts.push(value.trim());
|
|
56
|
+
seen.add(key.toLowerCase());
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
for (const [key, value] of Object.entries(narratives)) {
|
|
60
|
+
if (seen.has(key.toLowerCase()) || !isNonEmptyString(value)) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
parts.push(value.trim());
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (Array.isArray(plan.items)) {
|
|
67
|
+
for (const item of plan.items) {
|
|
68
|
+
const rec = asRecord(item);
|
|
69
|
+
if (rec === null) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const narrative = asRecord(rec.narrative);
|
|
73
|
+
const text = narrative?.Acceptance;
|
|
74
|
+
if (isNonEmptyString(text)) {
|
|
75
|
+
parts.push(text.trim());
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return parts.join("\n\n");
|
|
80
|
+
}
|
|
81
|
+
function hasAmbiguousReadings(clause) {
|
|
82
|
+
return clause.ambiguous === true && (clause.readings?.length ?? 0) > 0;
|
|
83
|
+
}
|
|
84
|
+
function extractClauseTokens(text) {
|
|
85
|
+
const found = [];
|
|
86
|
+
const seen = new Set();
|
|
87
|
+
const push = (raw) => {
|
|
88
|
+
const token = raw.trim();
|
|
89
|
+
if (token.length < 2 || seen.has(token)) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
seen.add(token);
|
|
93
|
+
found.push(token);
|
|
94
|
+
};
|
|
95
|
+
const quoted = /["'`]([^"'`\n]{2,80})["'`]/g;
|
|
96
|
+
let match = quoted.exec(text);
|
|
97
|
+
while (match !== null) {
|
|
98
|
+
push(match[1] ?? "");
|
|
99
|
+
match = quoted.exec(text);
|
|
100
|
+
}
|
|
101
|
+
const pathRe = /(?<![A-Za-z0-9_])((?:[A-Za-z0-9_.-]+\/)+[A-Za-z0-9_.-]+\.[A-Za-z][A-Za-z0-9]+)(?![A-Za-z0-9_])/g;
|
|
102
|
+
match = pathRe.exec(text);
|
|
103
|
+
while (match !== null) {
|
|
104
|
+
push(match[1] ?? "");
|
|
105
|
+
match = pathRe.exec(text);
|
|
106
|
+
}
|
|
107
|
+
const ident = /(?<![A-Za-z0-9_])([A-Za-z_][A-Za-z0-9_]*)(?![A-Za-z0-9_])/g;
|
|
108
|
+
match = ident.exec(text);
|
|
109
|
+
while (match !== null) {
|
|
110
|
+
const token = match[1] ?? "";
|
|
111
|
+
const camelOrPascal = /[a-z][A-Z]/.test(token) || /[A-Z][a-z]+[A-Z]/.test(token);
|
|
112
|
+
const codeShaped = camelOrPascal || token.includes("_");
|
|
113
|
+
if (codeShaped) {
|
|
114
|
+
push(token);
|
|
115
|
+
}
|
|
116
|
+
match = ident.exec(text);
|
|
117
|
+
}
|
|
118
|
+
return found;
|
|
119
|
+
}
|
|
120
|
+
/** Statement-traceable when the clause text or its identifiers appear in the statement. */
|
|
121
|
+
export function traceClauseProvenance(clause, statement) {
|
|
122
|
+
const normClause = clause.text.replace(/\s+/g, " ").trim();
|
|
123
|
+
const normStatement = statement.replace(/\s+/g, " ").trim();
|
|
124
|
+
if (normClause.length > 0 && normStatement.includes(normClause)) {
|
|
125
|
+
return "statement";
|
|
126
|
+
}
|
|
127
|
+
const tokens = extractClauseTokens(clause.text);
|
|
128
|
+
if (tokens.length === 0) {
|
|
129
|
+
return "implementation";
|
|
130
|
+
}
|
|
131
|
+
return tokens.every((token) => statement.includes(token)) ? "statement" : "implementation";
|
|
132
|
+
}
|
|
133
|
+
export function countClauseProvenance(clauses) {
|
|
134
|
+
let statement = 0;
|
|
135
|
+
let implementation = 0;
|
|
136
|
+
for (const clause of clauses) {
|
|
137
|
+
if (clause.provenance === "statement") {
|
|
138
|
+
statement += 1;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
implementation += 1;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return { statement, implementation };
|
|
145
|
+
}
|
|
146
|
+
/** Fail closed when every clause is implementation-provenance; else attest ambiguity. */
|
|
147
|
+
export function prepareClauseStamp(clauses, statement) {
|
|
148
|
+
const traced = clauses.map((clause) => ({
|
|
149
|
+
...clause,
|
|
150
|
+
provenance: traceClauseProvenance(clause, statement),
|
|
151
|
+
}));
|
|
152
|
+
const provenance_counts = countClauseProvenance(traced);
|
|
153
|
+
if (traced.length > 0 && provenance_counts.statement === 0) {
|
|
154
|
+
return {
|
|
155
|
+
ok: false,
|
|
156
|
+
clauses: traced,
|
|
157
|
+
provenance_counts,
|
|
158
|
+
remediation: CLAUSE_STAMP_IMPLEMENTATION_ONLY_REMEDIATION,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
ok: true,
|
|
163
|
+
clauses: traced,
|
|
164
|
+
provenance_counts,
|
|
165
|
+
ambiguity_attestation: traced.some(hasAmbiguousReadings) ? undefined : "none_found",
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const AMBIGUITY_ATTESTATION_MISSING = "verify:ac config error: missing ambiguity attestation; record at least one ambiguous: true clause with readings, or set ambiguity_attestation: none_found";
|
|
169
|
+
/** Config-error surface for verify:ac: attestation or an ambiguous clause with readings. */
|
|
170
|
+
export function evaluateAmbiguityAttestation(acceptance) {
|
|
171
|
+
const rec = asRecord(acceptance);
|
|
172
|
+
if (rec === null) {
|
|
173
|
+
return { ok: false, attested: false, kind: "missing", message: AMBIGUITY_ATTESTATION_MISSING };
|
|
174
|
+
}
|
|
175
|
+
const clauses = readAcceptanceClauses(rec);
|
|
176
|
+
if (clauses.some(hasAmbiguousReadings)) {
|
|
177
|
+
return { ok: true, attested: true, kind: "ambiguous-clause", message: "" };
|
|
178
|
+
}
|
|
179
|
+
if (rec.ambiguity_attestation === "none_found") {
|
|
180
|
+
return { ok: true, attested: true, kind: "none_found", message: "" };
|
|
181
|
+
}
|
|
182
|
+
return { ok: false, attested: false, kind: "missing", message: AMBIGUITY_ATTESTATION_MISSING };
|
|
183
|
+
}
|
|
184
|
+
function serializeTracedClauses(clauses) {
|
|
185
|
+
return serializeAcceptanceClauses(clauses).map((row, index) => ({
|
|
186
|
+
...row,
|
|
187
|
+
provenance: clauses[index]?.provenance ?? "implementation",
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Annotate stamped clauses with provenance and an ambiguity attestation.
|
|
192
|
+
* An all-implementation stamp is stripped (never sufficient alone).
|
|
193
|
+
*/
|
|
194
|
+
export function applyClauseQualityToPlan(plan) {
|
|
195
|
+
const existing = asRecord(plan.acceptance);
|
|
196
|
+
if (existing === null) {
|
|
197
|
+
return { applied: false, clauses: [], notice: "" };
|
|
198
|
+
}
|
|
199
|
+
const clauses = readAcceptanceClauses(existing);
|
|
200
|
+
if (clauses.length === 0) {
|
|
201
|
+
return { applied: false, clauses: [], notice: "" };
|
|
202
|
+
}
|
|
203
|
+
const prepared = prepareClauseStamp(clauses, collectTaskStatementFromPlan(plan));
|
|
204
|
+
if (!prepared.ok) {
|
|
205
|
+
const next = { ...existing };
|
|
206
|
+
delete next.clauses;
|
|
207
|
+
plan.acceptance = next;
|
|
208
|
+
return {
|
|
209
|
+
applied: false,
|
|
210
|
+
clauses: prepared.clauses,
|
|
211
|
+
notice: prepared.remediation ?? CLAUSE_STAMP_IMPLEMENTATION_ONLY_REMEDIATION,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const next = {
|
|
215
|
+
...existing,
|
|
216
|
+
clauses: serializeTracedClauses(prepared.clauses),
|
|
217
|
+
};
|
|
218
|
+
if (prepared.ambiguity_attestation !== undefined &&
|
|
219
|
+
existing.ambiguity_attestation === undefined) {
|
|
220
|
+
next.ambiguity_attestation = prepared.ambiguity_attestation;
|
|
221
|
+
}
|
|
222
|
+
plan.acceptance = next;
|
|
223
|
+
return {
|
|
224
|
+
applied: true,
|
|
225
|
+
clauses: prepared.clauses,
|
|
226
|
+
notice: formatAmbiguousClauseNotice(prepared.clauses),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
/** Ingest path: strip an implementation-only stamp and record the remediation. */
|
|
230
|
+
export function applyClauseQualityForIngest(plan) {
|
|
231
|
+
const quality = applyClauseQualityToPlan(plan);
|
|
232
|
+
if (!quality.applied && quality.notice.length > 0) {
|
|
233
|
+
const existing = asRecord(plan.acceptance);
|
|
234
|
+
if (existing !== null) {
|
|
235
|
+
plan.acceptance = {
|
|
236
|
+
...existing,
|
|
237
|
+
derived_reason: quality.notice,
|
|
238
|
+
quality_notice: quality.notice,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return quality;
|
|
243
|
+
}
|
|
244
|
+
function formatAmbiguousClauseNotice(clauses) {
|
|
245
|
+
const flagged = clauses.filter((clause) => clause.ambiguous);
|
|
246
|
+
const lines = [
|
|
247
|
+
`#3323 clause derivation stamped ${clauses.length} clause(s)` +
|
|
248
|
+
(flagged.length > 0 ? `; flagged-ambiguous: ${flagged.length}` : ""),
|
|
249
|
+
];
|
|
250
|
+
for (const clause of flagged) {
|
|
251
|
+
const chosen = clause.chosen_reading ?? 0;
|
|
252
|
+
const chosenPath = clause.readings?.[chosen]?.artifact_path ?? clause.artifact_path ?? "(none)";
|
|
253
|
+
lines.push(` clause ${clause.id}: two readings; chosen_reading=${chosen} (${chosenPath}) [headless #3323]`);
|
|
254
|
+
for (const [index, reading] of (clause.readings ?? []).entries()) {
|
|
255
|
+
lines.push(` reading ${index}: ${reading.artifact_path ?? "(none)"}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return lines.join("\n");
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Stamp derived clauses onto plan.acceptance in place. Headless: records
|
|
262
|
+
* chosen_reading, never blocks on a question.
|
|
263
|
+
*/
|
|
264
|
+
export function applyClauseDerivationToPlan(plan, options = {}) {
|
|
265
|
+
if (!needsClauseDerivation(plan.acceptance)) {
|
|
266
|
+
return {
|
|
267
|
+
applied: false,
|
|
268
|
+
clauses: readAcceptanceClauses(plan.acceptance),
|
|
269
|
+
notice: "",
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
const clauses = deriveAcceptanceClauses(collectTaskStatementFromPlan(plan));
|
|
273
|
+
if (clauses.length === 0) {
|
|
274
|
+
return { applied: false, clauses: [], notice: "" };
|
|
275
|
+
}
|
|
276
|
+
const existing = asRecord(plan.acceptance);
|
|
277
|
+
const commands = existing !== null && Array.isArray(existing.commands) ? existing.commands : [];
|
|
278
|
+
const hasCommands = commands.length > 0;
|
|
279
|
+
const noneStated = hasCommands ? existing?.none_stated === true : true;
|
|
280
|
+
const sourceRung = hasCommands && existing?.none_stated !== true
|
|
281
|
+
? isNonEmptyString(existing?.source_rung)
|
|
282
|
+
? existing.source_rung
|
|
283
|
+
: "stated"
|
|
284
|
+
: "derived";
|
|
285
|
+
const previousAcceptance = plan.acceptance;
|
|
286
|
+
plan.acceptance = {
|
|
287
|
+
...(existing ?? {}),
|
|
288
|
+
commands: hasCommands ? commands : [],
|
|
289
|
+
none_stated: noneStated,
|
|
290
|
+
source_rung: sourceRung,
|
|
291
|
+
derived_reason: `derived ${clauses.length} independently testable clauses from the task statement before product edit (#3323)`,
|
|
292
|
+
clauses: serializeAcceptanceClauses(clauses),
|
|
293
|
+
};
|
|
294
|
+
const quality = applyClauseQualityToPlan(plan);
|
|
295
|
+
if (!quality.applied) {
|
|
296
|
+
const existing = asRecord(previousAcceptance);
|
|
297
|
+
if (existing !== null && quality.notice.length > 0) {
|
|
298
|
+
plan.acceptance = {
|
|
299
|
+
...existing,
|
|
300
|
+
quality_notice: quality.notice,
|
|
301
|
+
derived_reason: quality.notice,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
plan.acceptance = previousAcceptance;
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
applied: false,
|
|
309
|
+
clauses: quality.clauses,
|
|
310
|
+
notice: quality.notice,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
if (options.emitStamp !== false && options.projectRoot !== undefined) {
|
|
314
|
+
emitAcceptanceStampFromPlan(options.projectRoot, plan);
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
applied: true,
|
|
318
|
+
clauses: quality.clauses,
|
|
319
|
+
notice: quality.notice,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
/** Stable fingerprint of plan.acceptance for first-write / material-change detection (#3355). */
|
|
323
|
+
export function acceptanceFingerprint(acceptance) {
|
|
324
|
+
const rec = asRecord(acceptance);
|
|
325
|
+
if (rec === null) {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
const commands = Array.isArray(rec.commands) ? rec.commands.length : 0;
|
|
329
|
+
const clauses = Array.isArray(rec.clauses)
|
|
330
|
+
? rec.clauses.map((entry, index) => {
|
|
331
|
+
const row = asRecord(entry);
|
|
332
|
+
if (row === null) {
|
|
333
|
+
return `${index}`;
|
|
334
|
+
}
|
|
335
|
+
return `${row.id ?? index}:${row.text ?? ""}:${row.artifact_path ?? ""}`;
|
|
336
|
+
})
|
|
337
|
+
: [];
|
|
338
|
+
const rung = typeof rec.source_rung === "string" ? rec.source_rung : "";
|
|
339
|
+
return `${rung}|${rec.none_stated === true}|${commands}|${clauses.join(";")}`;
|
|
340
|
+
}
|
|
341
|
+
/** True when next is a first write or a material change versus previous (#3355). */
|
|
342
|
+
export function isMaterialAcceptanceChange(previous, next) {
|
|
343
|
+
const nextFp = acceptanceFingerprint(next);
|
|
344
|
+
if (nextFp === null) {
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
return acceptanceFingerprint(previous) !== nextFp;
|
|
348
|
+
}
|
|
349
|
+
/** Fail-open acceptance_stamp emission (same contract as issue:ingest / #3355). */
|
|
350
|
+
export function emitAcceptanceStampFromPlan(projectRoot, plan, env = process.env) {
|
|
351
|
+
const rec = asRecord(plan);
|
|
352
|
+
if (rec === null) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const acceptance = asRecord(rec.acceptance);
|
|
356
|
+
if (acceptance === null) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
const dest = env[ENV_RUN_SUMMARY_PATH];
|
|
360
|
+
if (dest === undefined || dest.trim().length === 0) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
try {
|
|
364
|
+
const emitter = new RunSummaryEmitter({
|
|
365
|
+
projectRoot,
|
|
366
|
+
env,
|
|
367
|
+
component: "clause-derivation",
|
|
368
|
+
});
|
|
369
|
+
const commands = Array.isArray(acceptance.commands) ? acceptance.commands.length : 0;
|
|
370
|
+
const clauses = Array.isArray(acceptance.clauses) ? acceptance.clauses.length : 0;
|
|
371
|
+
const statement = collectTaskStatementFromPlan(rec);
|
|
372
|
+
const rawRows = Array.isArray(acceptance.clauses) ? acceptance.clauses : [];
|
|
373
|
+
const traced = readAcceptanceClauses(acceptance).map((clause, index) => {
|
|
374
|
+
const row = asRecord(rawRows[index]);
|
|
375
|
+
const stamped = row?.provenance === "statement" || row?.provenance === "implementation"
|
|
376
|
+
? row.provenance
|
|
377
|
+
: undefined;
|
|
378
|
+
return {
|
|
379
|
+
...clause,
|
|
380
|
+
provenance: stamped ??
|
|
381
|
+
(statement.trim().length > 0 ? traceClauseProvenance(clause, statement) : "statement"),
|
|
382
|
+
};
|
|
383
|
+
});
|
|
384
|
+
const payload = {
|
|
385
|
+
rung: typeof acceptance.source_rung === "string" ? acceptance.source_rung : "project_floor",
|
|
386
|
+
none_stated: acceptance.none_stated === true,
|
|
387
|
+
command_count: commands,
|
|
388
|
+
clause_count: clauses,
|
|
389
|
+
provenance_counts: countClauseProvenance(traced),
|
|
390
|
+
ambiguity_attested: evaluateAmbiguityAttestation(acceptance).attested,
|
|
391
|
+
};
|
|
392
|
+
emitter.emitAcceptanceStamp(payload);
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
// fail-open
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Emit acceptance_stamp when plan.acceptance is first written or materially changed.
|
|
400
|
+
* State-observed: callers pass the before/after blocks from any lifecycle verb (#3355).
|
|
401
|
+
*/
|
|
402
|
+
export function maybeEmitAcceptanceStampFromChange(projectRoot, previous, next, env = process.env) {
|
|
403
|
+
if (!isMaterialAcceptanceChange(previous, next)) {
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
emitAcceptanceStampFromPlan(projectRoot, { acceptance: next }, env);
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
//# sourceMappingURL=clause-derivation.js.map
|
package/dist/intake/index.d.ts
CHANGED
package/dist/intake/index.js
CHANGED
|
@@ -58,6 +58,8 @@ export declare function buildIssueVbrief(issue: Record<string, unknown>, status:
|
|
|
58
58
|
infoRootKey?: typeof LEGACY_INFO_ROOT_KEY | typeof MIGRATED_INFO_ROOT_KEY;
|
|
59
59
|
infoVersion?: string;
|
|
60
60
|
}): [Record<string, unknown>, string];
|
|
61
|
+
/** Include a refused-stamp remediation on the ingest result so it is not silent. */
|
|
62
|
+
export declare function formatIngestCreatedMessage(folder: string, filename: string, plan: unknown, dryRun?: boolean): string;
|
|
61
63
|
export declare function targetFilename(number: number, title: string, artifactSuffix?: typeof LEGACY_ARTIFACT_SUFFIX | typeof MIGRATED_ARTIFACT_SUFFIX): string;
|
|
62
64
|
export interface FetchIssueOptions {
|
|
63
65
|
readonly cwd?: string | null;
|
|
@@ -116,5 +118,5 @@ export declare function ingestSingleForAccept(n: number, repo: string, options?:
|
|
|
116
118
|
projectRoot?: string | null;
|
|
117
119
|
status?: IngestStatus;
|
|
118
120
|
cacheRoot?: string | null;
|
|
119
|
-
}): [IngestResult, string | null];
|
|
121
|
+
}): [IngestResult, string | null, string];
|
|
120
122
|
//# sourceMappingURL=issue-ingest.d.ts.map
|