openplanr 1.17.0 → 1.19.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/cli/commands/operate.d.ts +10 -1
- package/dist/cli/commands/operate.d.ts.map +1 -1
- package/dist/cli/commands/operate.js +55 -7
- package/dist/cli/commands/operate.js.map +1 -1
- package/dist/services/ai-service.d.ts +25 -0
- package/dist/services/ai-service.d.ts.map +1 -1
- package/dist/services/ai-service.js +38 -0
- package/dist/services/ai-service.js.map +1 -1
- package/dist/services/operate/advisors.d.ts +100 -4
- package/dist/services/operate/advisors.d.ts.map +1 -1
- package/dist/services/operate/advisors.js +450 -14
- package/dist/services/operate/advisors.js.map +1 -1
- package/dist/services/operate/config.d.ts +36 -0
- package/dist/services/operate/config.d.ts.map +1 -1
- package/dist/services/operate/config.js +94 -6
- package/dist/services/operate/config.js.map +1 -1
- package/dist/services/operate/doctor.d.ts.map +1 -1
- package/dist/services/operate/doctor.js +121 -1
- package/dist/services/operate/doctor.js.map +1 -1
- package/dist/services/operate/engine.d.ts +22 -6
- package/dist/services/operate/engine.d.ts.map +1 -1
- package/dist/services/operate/engine.js +20 -9
- package/dist/services/operate/engine.js.map +1 -1
- package/dist/services/operate/evidence-readiness.d.ts +11 -1
- package/dist/services/operate/evidence-readiness.d.ts.map +1 -1
- package/dist/services/operate/evidence-readiness.js +12 -2
- package/dist/services/operate/evidence-readiness.js.map +1 -1
- package/dist/services/operate/evidence.d.ts +30 -1
- package/dist/services/operate/evidence.d.ts.map +1 -1
- package/dist/services/operate/evidence.js +248 -10
- package/dist/services/operate/evidence.js.map +1 -1
- package/dist/services/operate/index.d.ts +2 -0
- package/dist/services/operate/index.d.ts.map +1 -1
- package/dist/services/operate/index.js +242 -41
- package/dist/services/operate/index.js.map +1 -1
- package/dist/services/operate/interaction/answer-service.d.ts +17 -0
- package/dist/services/operate/interaction/answer-service.d.ts.map +1 -1
- package/dist/services/operate/interaction/answer-service.js +49 -7
- package/dist/services/operate/interaction/answer-service.js.map +1 -1
- package/dist/services/operate/interaction/question-engine.d.ts.map +1 -1
- package/dist/services/operate/interaction/question-engine.js +9 -3
- package/dist/services/operate/interaction/question-engine.js.map +1 -1
- package/dist/services/operate/interaction/question-registry.d.ts +13 -0
- package/dist/services/operate/interaction/question-registry.d.ts.map +1 -1
- package/dist/services/operate/interaction/question-registry.js +116 -26
- package/dist/services/operate/interaction/question-registry.js.map +1 -1
- package/dist/services/operate/interaction/terminal-renderer.d.ts +12 -1
- package/dist/services/operate/interaction/terminal-renderer.d.ts.map +1 -1
- package/dist/services/operate/interaction/terminal-renderer.js +26 -15
- package/dist/services/operate/interaction/terminal-renderer.js.map +1 -1
- package/dist/services/operate/lifecycle.d.ts +8 -0
- package/dist/services/operate/lifecycle.d.ts.map +1 -1
- package/dist/services/operate/lifecycle.js +36 -2
- package/dist/services/operate/lifecycle.js.map +1 -1
- package/dist/services/operate/maintenance.d.ts +33 -1
- package/dist/services/operate/maintenance.d.ts.map +1 -1
- package/dist/services/operate/maintenance.js +467 -47
- package/dist/services/operate/maintenance.js.map +1 -1
- package/dist/services/operate/projection-persistence.d.ts +35 -7
- package/dist/services/operate/projection-persistence.d.ts.map +1 -1
- package/dist/services/operate/projection-persistence.js +122 -53
- package/dist/services/operate/projection-persistence.js.map +1 -1
- package/dist/services/operate/projection.d.ts +9 -1
- package/dist/services/operate/projection.d.ts.map +1 -1
- package/dist/services/operate/projection.js +12 -9
- package/dist/services/operate/projection.js.map +1 -1
- package/dist/services/operate/protocol.d.ts +4 -1
- package/dist/services/operate/protocol.d.ts.map +1 -1
- package/dist/services/operate/protocol.js.map +1 -1
- package/dist/services/operate/reports.d.ts +18 -0
- package/dist/services/operate/reports.d.ts.map +1 -1
- package/dist/services/operate/reports.js +29 -1
- package/dist/services/operate/reports.js.map +1 -1
- package/dist/services/operate/routes.d.ts +84 -0
- package/dist/services/operate/routes.d.ts.map +1 -1
- package/dist/services/operate/routes.js +519 -10
- package/dist/services/operate/routes.js.map +1 -1
- package/dist/services/operate/types.d.ts +25 -5
- package/dist/services/operate/types.d.ts.map +1 -1
- package/dist/services/operate/types.js.map +1 -1
- package/dist/services/runtime-manager-service.d.ts.map +1 -1
- package/dist/services/runtime-manager-service.js +25 -5
- package/dist/services/runtime-manager-service.js.map +1 -1
- package/package.json +2 -2
|
@@ -8,6 +8,9 @@ function ageHours(iso, now) {
|
|
|
8
8
|
export async function evaluateEvidenceReadiness(input) {
|
|
9
9
|
const now = input.now ?? new Date();
|
|
10
10
|
const registry = (await loadOperatingProtocol()).listOperatingRoles();
|
|
11
|
+
const postIndexRepositoryCount = input.missionEvidenceIndex
|
|
12
|
+
? input.missionEvidenceIndex.filter((item) => item.source === 'repository').length
|
|
13
|
+
: null;
|
|
11
14
|
const eligibleItems = input.evidence.items.filter((item) => !prepareAdvisorEvidenceText({
|
|
12
15
|
evidenceId: item.id,
|
|
13
16
|
digest: item.digest,
|
|
@@ -33,7 +36,14 @@ export async function evaluateEvidenceReadiness(input) {
|
|
|
33
36
|
const oldestAgeHours = matching.length > 0
|
|
34
37
|
? Math.max(...matching.map((item) => ageHours(item.collectedAt, now)))
|
|
35
38
|
: null;
|
|
36
|
-
|
|
39
|
+
// FR2: for a mission-mode evaluation, a `repository` requirement can be
|
|
40
|
+
// satisfied only by repository items the mission index actually retained —
|
|
41
|
+
// dot-prefixed/pattern-dropped repository items are unreachable by a bounded
|
|
42
|
+
// mission packet, so they cannot count toward readiness.
|
|
43
|
+
const observedItems = postIndexRepositoryCount !== null && requirement.source === 'repository'
|
|
44
|
+
? Math.min(matching.length, postIndexRepositoryCount)
|
|
45
|
+
: matching.length;
|
|
46
|
+
const satisfied = observedItems >= requirement.minimumItems &&
|
|
37
47
|
oldestAgeHours !== null &&
|
|
38
48
|
oldestAgeHours <= requirement.maxAgeHours &&
|
|
39
49
|
(requirement.observationWindow === 'current-state' ||
|
|
@@ -41,7 +51,7 @@ export async function evaluateEvidenceReadiness(input) {
|
|
|
41
51
|
matching.some((item) => item.observedFrom && item.observedTo));
|
|
42
52
|
return {
|
|
43
53
|
...structuredClone(requirement),
|
|
44
|
-
observedItems
|
|
54
|
+
observedItems,
|
|
45
55
|
oldestAgeHours,
|
|
46
56
|
satisfied,
|
|
47
57
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evidence-readiness.js","sourceRoot":"","sources":["../../../src/services/operate/evidence-readiness.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC/E,OAAO,EAAE,kBAAkB,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAChF,OAAO,EAEL,wBAAwB,EACxB,sBAAsB,
|
|
1
|
+
{"version":3,"file":"evidence-readiness.js","sourceRoot":"","sources":["../../../src/services/operate/evidence-readiness.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC/E,OAAO,EAAE,kBAAkB,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAChF,OAAO,EAEL,wBAAwB,EACxB,sBAAsB,GAKvB,MAAM,YAAY,CAAC;AAYpB,SAAS,QAAQ,CAAC,GAAW,EAAE,GAAS;IACtC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC;AAC5E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAAC,KAe/C;IACC,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC;IACpC,MAAM,QAAQ,GAAG,CACf,MAAM,qBAAqB,EAAE,CAC9B,CAAC,kBAAkB,EAA+B,CAAC;IACpD,MAAM,wBAAwB,GAAG,KAAK,CAAC,oBAAoB;QACzD,CAAC,CAAC,KAAK,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,KAAK,YAAY,CAAC,CAAC,MAAM;QAClF,CAAC,CAAC,IAAI,CAAC;IACT,MAAM,aAAa,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAC/C,CAAC,IAAI,EAAE,EAAE,CACP,CAAC,0BAA0B,CAAC;QAC1B,UAAU,EAAE,IAAI,CAAC,EAAE;QACnB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,KAAK,EAAE,IAAI,CAAC,OAAO,IAAI,EAAE;KAC1B,CAAC,CAAC,WAAW,CACjB,CAAC;IACF,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,MAAM,KAAK,GAAG,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;QAC9C,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,MAAM,CAAC,CAAC;QAC3D,MAAM,UAAU,GAAG,IAAI,EAAE,eAAe,CAAC,YAAY,IAAI;YACvD;gBACE,MAAM,EAAE,YAAY;gBACpB,UAAU,EAAE,CAAC,MAAM,CAAC;gBACpB,YAAY,EAAE,CAAC;gBACf,WAAW,EAAE,GAAG;gBAChB,iBAAiB,EAAE,eAAwB;gBAC3C,kBAAkB,EAAE,UAAmB;aACxC;SACF,CAAC;QACF,MAAM,YAAY,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE;YAClD,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CACnC,CAAC,IAAI,EAAE,EAAE,CACP,IAAI,CAAC,MAAM,KAAK,WAAW,CAAC,MAAM;gBAClC,WAAW,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;gBACvE,kBAAkB,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAC5E,CAAC;YACF,MAAM,cAAc,GAClB,QAAQ,CAAC,MAAM,GAAG,CAAC;gBACjB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,CAAC;gBACtE,CAAC,CAAC,IAAI,CAAC;YACX,wEAAwE;YACxE,2EAA2E;YAC3E,6EAA6E;YAC7E,yDAAyD;YACzD,MAAM,aAAa,GACjB,wBAAwB,KAAK,IAAI,IAAI,WAAW,CAAC,MAAM,KAAK,YAAY;gBACtE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,wBAAwB,CAAC;gBACrD,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;YACtB,MAAM,SAAS,GACb,aAAa,IAAI,WAAW,CAAC,YAAY;gBACzC,cAAc,KAAK,IAAI;gBACvB,cAAc,IAAI,WAAW,CAAC,WAAW;gBACzC,CAAC,WAAW,CAAC,iBAAiB,KAAK,eAAe;oBAChD,WAAW,CAAC,iBAAiB,KAAK,eAAe;oBACjD,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;YACnE,OAAO;gBACL,GAAG,eAAe,CAAC,WAAW,CAAC;gBAC/B,aAAa;gBACb,cAAc;gBACd,SAAS;aACV,CAAC;QACJ,CAAC,CAAC,CAAC;QACH,MAAM,KAAK,GACT,CAAC,IAAI,EAAE,eAAe,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,KAAK;YAC9C,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,WAAW,CAAC,SAAS,CAAC;YAC3D,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;QACjE,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;QAC3E,OAAO;YACL,MAAM;YACN,SAAS,EAAE,KAAK,CAAC,CAAC,CAAE,OAAiB,CAAC,CAAC,CAAE,eAAyB;YAClE,YAAY;YACZ,eAAe,EAAE,YAAY;iBAC1B,MAAM,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,SAAS,CAAC;iBAC/C,GAAG,CACF,CAAC,WAAW,EAAE,EAAE,CACd,GAAG,WAAW,CAAC,MAAM,IAAI,WAAW,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,WAAW,CAAC,aAAa,IAAI,WAAW,CAAC,YAAY,GAAG,CACzH;YACH,YAAY,EAAE,aAAa;iBACxB,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CACf,YAAY,CAAC,IAAI,CACf,CAAC,WAAW,EAAE,EAAE,CACd,WAAW,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;gBAClC,WAAW,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAC1E,CACF;iBACA,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;iBACtB,IAAI,EAAE;YACT,gBAAgB,EAAE,KAAK;YACvB,KAAK;SACN,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,MAAM,SAAS,GAA+B;QAC5C,IAAI,EAAE,8BAA8B;QACpC,aAAa,EAAE,sBAAsB;QACrC,eAAe,EAAE,wBAAwB;QACzC,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,WAAW,EAAE,eAAe,CAAC;YAC3B,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,WAAW;YACpC,KAAK,EAAE,KAAK,CAAC,YAAY;YACzB,2BAA2B,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE;SACzE,CAAC;QACF,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE;QAC9B,KAAK;KACN,CAAC;IACF,OAAO,uBAAuB,CAAC,8BAA8B,EAAE,SAAS,CAAC,CAAC;AAC5E,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ReadOnlyFetch } from './read-only-providers.js';
|
|
2
|
-
import { type CollectedEvidenceItem, type EvidenceBudget, type OperatingDataGap, type OperatingEvidence, type OperatingEvidenceIndexItem, type OperatingEvidenceItem, type OperatingSensitivity, type OperatingWorkspaceManifest } from './types.js';
|
|
2
|
+
import { type CollectedEvidenceItem, type EvidenceBudget, type OperatingDataGap, type OperatingEvidence, type OperatingEvidenceIndexItem, type OperatingEvidenceItem, type OperatingRoleId, type OperatingSensitivity, type OperatingWorkspaceManifest } from './types.js';
|
|
3
3
|
export interface EvidenceCollectionInput {
|
|
4
4
|
projectRoot: string;
|
|
5
5
|
cycleId: string;
|
|
@@ -62,6 +62,21 @@ export declare function unqualifiedCommercialEvidenceGaps(input: {
|
|
|
62
62
|
owner: string;
|
|
63
63
|
now: string;
|
|
64
64
|
}): Promise<OperatingDataGap[]>;
|
|
65
|
+
/**
|
|
66
|
+
* FR2: open a governed evidence gap for every mission role whose POST-index
|
|
67
|
+
* evidence retained zero `source: 'repository'` items. A role that requires
|
|
68
|
+
* repository code must never dispatch "ready" on an empty evidence base — the
|
|
69
|
+
* pre-index readiness gate can pass while the mission index built moments later
|
|
70
|
+
* loses every repository item to the dot-prefixed/pattern filter. Each gap names
|
|
71
|
+
* the affected role and the reason so the loss is visible instead of silent.
|
|
72
|
+
*/
|
|
73
|
+
export declare function starvedRoleEvidenceGaps(input: {
|
|
74
|
+
cycleId: string;
|
|
75
|
+
roleIds: readonly OperatingRoleId[];
|
|
76
|
+
owner: string;
|
|
77
|
+
now: string;
|
|
78
|
+
startingGapNumber?: number;
|
|
79
|
+
}): Promise<OperatingDataGap[]>;
|
|
65
80
|
export declare function evidenceProjectionSources(evidence: OperatingEvidence): Array<{
|
|
66
81
|
id: string;
|
|
67
82
|
freshness: OperatingEvidenceItem['freshness'];
|
|
@@ -81,6 +96,20 @@ export declare function evidenceProjectionSources(evidence: OperatingEvidence):
|
|
|
81
96
|
export declare function buildOperatingEvidenceIndex(evidence: OperatingEvidence, options?: {
|
|
82
97
|
sensitivityCeiling?: OperatingSensitivity;
|
|
83
98
|
}): OperatingEvidenceIndexItem[];
|
|
99
|
+
/**
|
|
100
|
+
* FR2: count — using the exact predicates `buildOperatingEvidenceIndex` filters
|
|
101
|
+
* on — the path-referenced evidence items a mission evidence index would drop:
|
|
102
|
+
* `pathPattern` for dot-prefixed / pattern-rejected paths, and `sensitivity` for
|
|
103
|
+
* items above a supplied ceiling. Integration records (github/linear) and
|
|
104
|
+
* git-by-revision items are never a "loss" (they are not path pointers), so they
|
|
105
|
+
* are not counted. The caller surfaces the counts as evidence-bundle warnings.
|
|
106
|
+
*/
|
|
107
|
+
export declare function missionEvidenceIndexDrops(items: readonly OperatingEvidenceItem[], options?: {
|
|
108
|
+
sensitivityCeiling?: OperatingSensitivity;
|
|
109
|
+
}): {
|
|
110
|
+
pathPattern: number;
|
|
111
|
+
sensitivity: number;
|
|
112
|
+
};
|
|
84
113
|
/**
|
|
85
114
|
* The read roots a mission agent may traverse, derived from the top-level
|
|
86
115
|
* directory segment of every path-referenced index item. Schema-valid and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evidence.d.ts","sourceRoot":"","sources":["../../../src/services/operate/evidence.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"evidence.d.ts","sourceRoot":"","sources":["../../../src/services/operate/evidence.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { loadConfig } from '../config-service.js';
|
|
4
4
|
import { resolveApiKey } from '../credentials-service.js';
|
|
5
5
|
import { canonicalDigest, canonicalize } from './canonical.js';
|
|
6
|
+
import { OperatingEventStore } from './event-store.js';
|
|
6
7
|
import { createEvidenceDiagnostic } from './evidence-diagnostics.js';
|
|
7
8
|
import { readImportedEvidenceFile } from './evidence-import.js';
|
|
8
9
|
import { assertOperatingArtifact } from './protocol.js';
|
|
@@ -70,15 +71,112 @@ function descriptorById(workspace) {
|
|
|
70
71
|
entry,
|
|
71
72
|
]));
|
|
72
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* The first path segment of a repository-relative path (its top-level
|
|
76
|
+
* directory), or null for a root-level file. Drives per-directory round-robin
|
|
77
|
+
* fairness and the truncation diagnostics.
|
|
78
|
+
*/
|
|
79
|
+
function topLevelSegment(relativePath) {
|
|
80
|
+
const slash = relativePath.indexOf('/');
|
|
81
|
+
if (slash < 0)
|
|
82
|
+
return null;
|
|
83
|
+
const top = relativePath.slice(0, slash);
|
|
84
|
+
return top.length > 0 ? top : null;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Map each tracked path to the Unix-second timestamp of the most recent commit
|
|
88
|
+
* that touched it, from a single `git log --name-only` walk (newest-first, so a
|
|
89
|
+
* path's first occurrence is its latest change). A leading NUL marks the
|
|
90
|
+
* timestamp header line so a filename can never be mistaken for a timestamp.
|
|
91
|
+
* Missing or empty history yields an empty map; callers then fall back to a
|
|
92
|
+
* path-stable order, keeping the evidence digest reproducible.
|
|
93
|
+
*/
|
|
94
|
+
async function gitRecencyMap(root, deadline) {
|
|
95
|
+
const remaining = deadline - Date.now();
|
|
96
|
+
if (remaining <= 0)
|
|
97
|
+
return new Map();
|
|
98
|
+
const output = await executeGitReadOnly(root, ['-c', 'core.quotePath=false', 'log', '--no-renames', '--pretty=format:%x00%ct', '--name-only'], { timeoutMs: remaining }).catch(() => '');
|
|
99
|
+
const recency = new Map();
|
|
100
|
+
let current = 0;
|
|
101
|
+
for (const line of output.split('\n')) {
|
|
102
|
+
if (line.startsWith('\0')) {
|
|
103
|
+
current = Number.parseInt(line.slice(1), 10) || 0;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (line.length === 0)
|
|
107
|
+
continue;
|
|
108
|
+
if (!recency.has(line))
|
|
109
|
+
recency.set(line, current);
|
|
110
|
+
}
|
|
111
|
+
return recency;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* FR3: order candidate paths for a fair, prioritized capped walk. Files are
|
|
115
|
+
* grouped by top-level directory; within a group the most recently changed come
|
|
116
|
+
* first (git recency, stable path tiebreak); product (non-dot) directories are
|
|
117
|
+
* round-robined to exhaustion BEFORE any dot-prefixed/planning tree, so a capped
|
|
118
|
+
* walk samples every product directory and dot-config never consumes the cap
|
|
119
|
+
* before product code. Deterministic: identical inputs yield an identical order.
|
|
120
|
+
*/
|
|
121
|
+
function orderCandidatesFairly(names, recency) {
|
|
122
|
+
const groups = new Map();
|
|
123
|
+
for (const name of names) {
|
|
124
|
+
const key = topLevelSegment(name) ?? '';
|
|
125
|
+
const bucket = groups.get(key);
|
|
126
|
+
if (bucket)
|
|
127
|
+
bucket.push(name);
|
|
128
|
+
else
|
|
129
|
+
groups.set(key, [name]);
|
|
130
|
+
}
|
|
131
|
+
for (const bucket of groups.values()) {
|
|
132
|
+
bucket.sort((left, right) => (recency.get(right) ?? 0) - (recency.get(left) ?? 0) ||
|
|
133
|
+
(left < right ? -1 : left > right ? 1 : 0));
|
|
134
|
+
}
|
|
135
|
+
const isPlanningGroup = (key) => key.startsWith('.');
|
|
136
|
+
const productGroups = [...groups.keys()].filter((key) => !isPlanningGroup(key)).sort();
|
|
137
|
+
const planningGroups = [...groups.keys()].filter(isPlanningGroup).sort();
|
|
138
|
+
const roundRobin = (keys) => {
|
|
139
|
+
const ordered = [];
|
|
140
|
+
const cursors = new Map(keys.map((key) => [key, 0]));
|
|
141
|
+
for (let active = keys.length; active > 0;) {
|
|
142
|
+
active = 0;
|
|
143
|
+
for (const key of keys) {
|
|
144
|
+
const bucket = groups.get(key) ?? [];
|
|
145
|
+
const cursor = cursors.get(key) ?? 0;
|
|
146
|
+
if (cursor < bucket.length) {
|
|
147
|
+
ordered.push(bucket[cursor]);
|
|
148
|
+
cursors.set(key, cursor + 1);
|
|
149
|
+
active += 1;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return ordered;
|
|
154
|
+
};
|
|
155
|
+
return [...roundRobin(productGroups), ...roundRobin(planningGroups)];
|
|
156
|
+
}
|
|
73
157
|
async function repositoryItems(roots, workspace, budgets, now, options) {
|
|
74
158
|
const descriptors = descriptorById(workspace);
|
|
75
159
|
const results = [];
|
|
76
|
-
|
|
160
|
+
// FR3: the repository (product code) and planr (planning tree) providers no
|
|
161
|
+
// longer share one undifferentiated `maxFiles` counter. Product code draws the
|
|
162
|
+
// full file budget; the `.planr/` tree draws an independent, smaller allotment
|
|
163
|
+
// so a large planning tree can never consume the cap before product code.
|
|
164
|
+
const repositoryFileBudget = budgets.maxFiles;
|
|
165
|
+
const planrFileBudget = Math.max(1, Math.floor(budgets.maxFiles / 4));
|
|
166
|
+
let repositoryFiles = 0;
|
|
167
|
+
let planrFiles = 0;
|
|
77
168
|
let bytes = 0;
|
|
78
169
|
let truncated = false;
|
|
79
|
-
|
|
170
|
+
let lastPathReached = null;
|
|
171
|
+
const unscannedTopLevel = new Set();
|
|
172
|
+
const componentIds = Object.keys(roots.roots).sort();
|
|
173
|
+
for (let componentIndex = 0; componentIndex < componentIds.length; componentIndex += 1) {
|
|
174
|
+
const componentId = componentIds[componentIndex];
|
|
175
|
+
const root = roots.roots[componentId];
|
|
80
176
|
if (Date.now() >= options.deadline) {
|
|
81
177
|
truncated = true;
|
|
178
|
+
for (const remaining of componentIds.slice(componentIndex))
|
|
179
|
+
unscannedTopLevel.add(remaining);
|
|
82
180
|
break;
|
|
83
181
|
}
|
|
84
182
|
const descriptor = descriptors.get(componentId);
|
|
@@ -88,20 +186,42 @@ async function repositoryItems(roots, workspace, budgets, now, options) {
|
|
|
88
186
|
const names = (await executeGitReadOnly(canonicalRoot, ['ls-files', '-z']))
|
|
89
187
|
.split('\0')
|
|
90
188
|
.filter(Boolean)
|
|
91
|
-
.filter((name) => TEXT_EXTENSIONS.has(path.extname(name).toLowerCase()))
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
189
|
+
.filter((name) => TEXT_EXTENSIONS.has(path.extname(name).toLowerCase()));
|
|
190
|
+
const recency = await gitRecencyMap(canonicalRoot, options.deadline);
|
|
191
|
+
const ordered = orderCandidatesFairly(names, recency);
|
|
192
|
+
const pendingTop = new Set(ordered.map((name) => topLevelSegment(name)).filter((top) => top !== null));
|
|
193
|
+
let stopped = false;
|
|
194
|
+
for (const relativePath of ordered) {
|
|
195
|
+
if (Date.now() >= options.deadline || results.length >= budgets.maxItems) {
|
|
97
196
|
truncated = true;
|
|
197
|
+
stopped = true;
|
|
198
|
+
for (const top of pendingTop)
|
|
199
|
+
unscannedTopLevel.add(top);
|
|
98
200
|
break;
|
|
99
201
|
}
|
|
202
|
+
const reachedTop = topLevelSegment(relativePath);
|
|
203
|
+
if (reachedTop)
|
|
204
|
+
pendingTop.delete(reachedTop);
|
|
100
205
|
const isPlanr = relativePath.startsWith('.planr/');
|
|
101
206
|
if ((!options.includeRepository && !isPlanr) ||
|
|
102
207
|
(isPlanr && !options.includePlanr && !options.includeRepository)) {
|
|
103
208
|
continue;
|
|
104
209
|
}
|
|
210
|
+
// FR3: enforce the split file allotments. A planr file over the planr
|
|
211
|
+
// allotment, or a product file over the repository allotment, is skipped
|
|
212
|
+
// (and the walk marked truncated) rather than consuming the other
|
|
213
|
+
// provider's budget. Product code is ordered first, so it claims its cap
|
|
214
|
+
// before any planning tree is reached.
|
|
215
|
+
if (isPlanr) {
|
|
216
|
+
if (planrFiles >= planrFileBudget) {
|
|
217
|
+
truncated = true;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
else if (repositoryFiles >= repositoryFileBudget) {
|
|
222
|
+
truncated = true;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
105
225
|
const target = path.join(canonicalRoot, relativePath);
|
|
106
226
|
const directInfo = await lstat(target).catch(() => null);
|
|
107
227
|
if (!directInfo)
|
|
@@ -119,13 +239,20 @@ async function repositoryItems(roots, workspace, budgets, now, options) {
|
|
|
119
239
|
continue;
|
|
120
240
|
if (bytes + info.size > budgets.maxBytes) {
|
|
121
241
|
truncated = true;
|
|
242
|
+
stopped = true;
|
|
243
|
+
for (const top of pendingTop)
|
|
244
|
+
unscannedTopLevel.add(top);
|
|
122
245
|
break;
|
|
123
246
|
}
|
|
124
247
|
const content = await readFile(resolvedTarget, 'utf8').catch(() => null);
|
|
125
248
|
if (content === null || content.includes('\0'))
|
|
126
249
|
continue;
|
|
127
|
-
|
|
250
|
+
if (isPlanr)
|
|
251
|
+
planrFiles += 1;
|
|
252
|
+
else
|
|
253
|
+
repositoryFiles += 1;
|
|
128
254
|
bytes += Buffer.byteLength(content);
|
|
255
|
+
lastPathReached = relativePath;
|
|
129
256
|
const collectedAt = now.toISOString();
|
|
130
257
|
const repository = {
|
|
131
258
|
componentId,
|
|
@@ -184,8 +311,15 @@ async function repositoryItems(roots, workspace, budgets, now, options) {
|
|
|
184
311
|
});
|
|
185
312
|
}
|
|
186
313
|
}
|
|
314
|
+
if (stopped)
|
|
315
|
+
break;
|
|
187
316
|
}
|
|
188
|
-
return {
|
|
317
|
+
return {
|
|
318
|
+
items: results,
|
|
319
|
+
truncated,
|
|
320
|
+
lastPathReached,
|
|
321
|
+
unscannedTopLevel: [...unscannedTopLevel].sort(),
|
|
322
|
+
};
|
|
189
323
|
}
|
|
190
324
|
async function gitItems(roots, workspace, now, deadline) {
|
|
191
325
|
const descriptors = descriptorById(workspace);
|
|
@@ -749,6 +883,26 @@ function inferImportedCommercialMetric(content, format) {
|
|
|
749
883
|
function incrementalEvidencePath(input, key) {
|
|
750
884
|
return path.join(resolveOperatingPaths(input.projectRoot, { localRoot: input.localRoot }).evidence, 'incremental', `${key.slice('sha256:'.length)}.json`);
|
|
751
885
|
}
|
|
886
|
+
/**
|
|
887
|
+
* FR4: the board's identity is the hash of its event-chain genesis event. The
|
|
888
|
+
* incremental cache key incorporates it so a board re-inited at the same path —
|
|
889
|
+
* whose workspace components (remote/branch) are unchanged and would otherwise
|
|
890
|
+
* compute an identical key — never reuses a prior generation's baseline. Returns
|
|
891
|
+
* '' when no committed chain exists yet or it cannot be read (the collection then
|
|
892
|
+
* behaves exactly as before this change).
|
|
893
|
+
*/
|
|
894
|
+
async function evidenceBoardIdentity(input) {
|
|
895
|
+
try {
|
|
896
|
+
const { events } = await new OperatingEventStore(input.projectRoot, {
|
|
897
|
+
localRoot: input.localRoot,
|
|
898
|
+
}).replay();
|
|
899
|
+
const genesis = events.find((event) => event.previousEventHash === null) ?? events[0];
|
|
900
|
+
return genesis?.eventHash ?? '';
|
|
901
|
+
}
|
|
902
|
+
catch {
|
|
903
|
+
return '';
|
|
904
|
+
}
|
|
905
|
+
}
|
|
752
906
|
async function readIncrementalEvidence(input, key) {
|
|
753
907
|
if (!input.incremental)
|
|
754
908
|
return null;
|
|
@@ -757,6 +911,13 @@ async function readIncrementalEvidence(input, key) {
|
|
|
757
911
|
if (record.implementation !== 'openplanr-operate-incremental-evidence' || record.key !== key) {
|
|
758
912
|
return null;
|
|
759
913
|
}
|
|
914
|
+
// FR4: honor the baseline's own already-written workspaceDigest. A record
|
|
915
|
+
// whose captured workspace no longer matches the current one is treated as
|
|
916
|
+
// no-baseline so the cycle recollects deep, rather than trusting a snapshot
|
|
917
|
+
// bound to a superseded workspace (for example, a prior board generation).
|
|
918
|
+
if (record.workspaceDigest !== input.workspace.workspaceDigest) {
|
|
919
|
+
return null;
|
|
920
|
+
}
|
|
760
921
|
await assertOperatingArtifact('operating-evidence', record.evidence);
|
|
761
922
|
return record;
|
|
762
923
|
}
|
|
@@ -829,6 +990,7 @@ export async function collectOperatingEvidence(input) {
|
|
|
829
990
|
const now = input.now ?? new Date();
|
|
830
991
|
const deadline = started + input.budgets.maxDurationMs;
|
|
831
992
|
const incrementalKey = canonicalDigest({
|
|
993
|
+
boardIdentity: await evidenceBoardIdentity(input),
|
|
832
994
|
workspaceComponents: [input.workspace.controlRepository, ...input.workspace.components]
|
|
833
995
|
.map((component) => ({
|
|
834
996
|
componentId: component.componentId,
|
|
@@ -853,6 +1015,13 @@ export async function collectOperatingEvidence(input) {
|
|
|
853
1015
|
});
|
|
854
1016
|
raw.push(...repository.items);
|
|
855
1017
|
truncated ||= repository.truncated;
|
|
1018
|
+
// FR2: a `maxFiles`/duration/byte truncation is no longer a silent boolean.
|
|
1019
|
+
// Name the last path reached and the top-level directories the capped walk
|
|
1020
|
+
// never scanned so starvation is visible from the cycle warnings alone.
|
|
1021
|
+
if (repository.truncated &&
|
|
1022
|
+
(repository.lastPathReached || repository.unscannedTopLevel.length > 0)) {
|
|
1023
|
+
warnings.push(`Repository evidence collection was truncated at the configured caps; last path reached: ${repository.lastPathReached ?? 'none'}; top-level directories left unscanned: ${repository.unscannedTopLevel.length > 0 ? repository.unscannedTopLevel.join(', ') : 'none'}.`);
|
|
1024
|
+
}
|
|
856
1025
|
}
|
|
857
1026
|
if (input.providers.includes('git') && Date.now() < deadline) {
|
|
858
1027
|
const git = await gitItems(roots, input.workspace, now, deadline);
|
|
@@ -961,6 +1130,18 @@ export async function collectOperatingEvidence(input) {
|
|
|
961
1130
|
truncated = true;
|
|
962
1131
|
warnings.push('Evidence collection reached the configured duration budget.');
|
|
963
1132
|
}
|
|
1133
|
+
// FR2: report the mission-index path-pattern loss up front. The mission
|
|
1134
|
+
// evidence index forbids dot-prefixed/pattern-rejected paths, so those items —
|
|
1135
|
+
// though retained in the full evidence set — can never be referenced by a
|
|
1136
|
+
// bounded mission packet. Counting the loss here (rather than dropping it
|
|
1137
|
+
// silently inside `buildOperatingEvidenceIndex`) is what makes a starved
|
|
1138
|
+
// mission collection observable from the cycle warnings.
|
|
1139
|
+
const missionIndexDrops = missionEvidenceIndexDrops(flattened, {
|
|
1140
|
+
sensitivityCeiling: input.sensitivityCeiling,
|
|
1141
|
+
});
|
|
1142
|
+
if (missionIndexDrops.pathPattern > 0) {
|
|
1143
|
+
warnings.push(`${missionIndexDrops.pathPattern} evidence item(s) cannot be represented in the mission evidence index because their paths are dot-prefixed or pattern-rejected; they remain in the full evidence set but no bounded mission packet can reference them.`);
|
|
1144
|
+
}
|
|
964
1145
|
const sourceIds = [...new Set(input.providers)].sort();
|
|
965
1146
|
const delta = buildEvidenceDelta({
|
|
966
1147
|
incremental: Boolean(input.incremental),
|
|
@@ -1051,6 +1232,36 @@ export async function unqualifiedCommercialEvidenceGaps(input) {
|
|
|
1051
1232
|
await Promise.all(gaps.map((gap) => assertOperatingArtifact('operating-data-gap', gap)));
|
|
1052
1233
|
return gaps;
|
|
1053
1234
|
}
|
|
1235
|
+
/**
|
|
1236
|
+
* FR2: open a governed evidence gap for every mission role whose POST-index
|
|
1237
|
+
* evidence retained zero `source: 'repository'` items. A role that requires
|
|
1238
|
+
* repository code must never dispatch "ready" on an empty evidence base — the
|
|
1239
|
+
* pre-index readiness gate can pass while the mission index built moments later
|
|
1240
|
+
* loses every repository item to the dot-prefixed/pattern filter. Each gap names
|
|
1241
|
+
* the affected role and the reason so the loss is visible instead of silent.
|
|
1242
|
+
*/
|
|
1243
|
+
export async function starvedRoleEvidenceGaps(input) {
|
|
1244
|
+
const roleIds = [...new Set(input.roleIds)].sort();
|
|
1245
|
+
const start = input.startingGapNumber ?? 1;
|
|
1246
|
+
const gaps = roleIds.map((roleId, offset) => ({
|
|
1247
|
+
kind: 'operating-data-gap',
|
|
1248
|
+
schemaVersion: OPERATE_SCHEMA_VERSION,
|
|
1249
|
+
protocolVersion: OPERATE_PROTOCOL_VERSION,
|
|
1250
|
+
id: `GAP-${String(start + offset).padStart(3, '0')}`,
|
|
1251
|
+
cycleId: input.cycleId,
|
|
1252
|
+
question: `What repository evidence can ${roleId} read after mission-index filtering removed every repository item?`,
|
|
1253
|
+
reason: 'The mission evidence index retained zero repository items after dot-prefixed/pattern and sensitivity filtering, so this repository-dependent role was gated not-ready instead of dispatching on an empty evidence base.',
|
|
1254
|
+
unblocks: [],
|
|
1255
|
+
affectedRoles: [roleId],
|
|
1256
|
+
status: 'open',
|
|
1257
|
+
owner: input.owner,
|
|
1258
|
+
evidenceRefs: [],
|
|
1259
|
+
createdAt: input.now,
|
|
1260
|
+
updatedAt: input.now,
|
|
1261
|
+
}));
|
|
1262
|
+
await Promise.all(gaps.map((gap) => assertOperatingArtifact('operating-data-gap', gap)));
|
|
1263
|
+
return gaps;
|
|
1264
|
+
}
|
|
1054
1265
|
export function evidenceProjectionSources(evidence) {
|
|
1055
1266
|
return evidence.sources.map((source) => ({
|
|
1056
1267
|
id: source.id,
|
|
@@ -1166,6 +1377,33 @@ export function buildOperatingEvidenceIndex(evidence, options = {}) {
|
|
|
1166
1377
|
return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
|
|
1167
1378
|
});
|
|
1168
1379
|
}
|
|
1380
|
+
/**
|
|
1381
|
+
* FR2: count — using the exact predicates `buildOperatingEvidenceIndex` filters
|
|
1382
|
+
* on — the path-referenced evidence items a mission evidence index would drop:
|
|
1383
|
+
* `pathPattern` for dot-prefixed / pattern-rejected paths, and `sensitivity` for
|
|
1384
|
+
* items above a supplied ceiling. Integration records (github/linear) and
|
|
1385
|
+
* git-by-revision items are never a "loss" (they are not path pointers), so they
|
|
1386
|
+
* are not counted. The caller surfaces the counts as evidence-bundle warnings.
|
|
1387
|
+
*/
|
|
1388
|
+
export function missionEvidenceIndexDrops(items, options = {}) {
|
|
1389
|
+
let pathPattern = 0;
|
|
1390
|
+
let sensitivity = 0;
|
|
1391
|
+
for (const item of items) {
|
|
1392
|
+
if (options.sensitivityCeiling &&
|
|
1393
|
+
compareSensitivity(item.sensitivity, options.sensitivityCeiling) > 0) {
|
|
1394
|
+
sensitivity += 1;
|
|
1395
|
+
continue;
|
|
1396
|
+
}
|
|
1397
|
+
const source = item.source;
|
|
1398
|
+
if (!INDEX_SOURCE_CLASSIFICATION[source])
|
|
1399
|
+
continue;
|
|
1400
|
+
if (source === 'repository' || source === 'planr' || source === 'file-import') {
|
|
1401
|
+
if (evidenceIndexPath(item.location) === null)
|
|
1402
|
+
pathPattern += 1;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
return { pathPattern, sensitivity };
|
|
1406
|
+
}
|
|
1169
1407
|
/**
|
|
1170
1408
|
* The read roots a mission agent may traverse, derived from the top-level
|
|
1171
1409
|
* directory segment of every path-referenced index item. Schema-valid and
|