@deftai/directive-core 0.75.0 → 0.77.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.
Files changed (74) hide show
  1. package/dist/agents-md-budget/evaluate.d.ts +8 -1
  2. package/dist/agents-md-budget/evaluate.js +77 -22
  3. package/dist/agents-md-budget/skill-frontmatter.d.ts +2 -0
  4. package/dist/agents-md-budget/skill-frontmatter.js +4 -3
  5. package/dist/cache/operations.d.ts +2 -0
  6. package/dist/cache/operations.js +16 -1
  7. package/dist/content-contracts/skills/skill-frontmatter.js +4 -0
  8. package/dist/doctor/constants.js +3 -3
  9. package/dist/doctor/index.d.ts +1 -0
  10. package/dist/doctor/index.js +1 -0
  11. package/dist/doctor/main.js +2 -1
  12. package/dist/doctor/payload-staleness.js +62 -63
  13. package/dist/doctor/release-availability.d.ts +28 -0
  14. package/dist/doctor/release-availability.js +35 -0
  15. package/dist/eval/crud-telemetry.d.ts +5 -4
  16. package/dist/eval/crud-telemetry.js +9 -5
  17. package/dist/eval/health.d.ts +5 -4
  18. package/dist/eval/health.js +24 -16
  19. package/dist/eval/readback.js +2 -8
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.js +1 -0
  22. package/dist/init-deposit/scaffold.js +28 -17
  23. package/dist/intake/issue-ingest.js +23 -4
  24. package/dist/metrics/index.d.ts +2 -0
  25. package/dist/metrics/index.js +2 -0
  26. package/dist/metrics/resolve-metrics-home.d.ts +50 -0
  27. package/dist/metrics/resolve-metrics-home.js +125 -0
  28. package/dist/packaging/openpackage-tiers.d.ts +12 -0
  29. package/dist/packaging/openpackage-tiers.js +42 -0
  30. package/dist/plan-sequence/index.d.ts +3 -0
  31. package/dist/plan-sequence/index.js +3 -0
  32. package/dist/plan-sequence/store.d.ts +6 -0
  33. package/dist/plan-sequence/store.js +29 -0
  34. package/dist/plan-sequence/types.d.ts +79 -0
  35. package/dist/plan-sequence/types.js +220 -0
  36. package/dist/preflight/evaluate.d.ts +5 -2
  37. package/dist/preflight/evaluate.js +9 -4
  38. package/dist/preflight/index.d.ts +1 -1
  39. package/dist/preflight/index.js +1 -1
  40. package/dist/release/pipeline-fixture.d.ts +3 -0
  41. package/dist/release/pipeline-fixture.js +11 -0
  42. package/dist/release/pipeline.js +4 -0
  43. package/dist/release/version.d.ts +2 -0
  44. package/dist/release/version.js +42 -0
  45. package/dist/release-e2e/npm-ops.js +20 -6
  46. package/dist/scope/transition.js +17 -2
  47. package/dist/scope/vbrief-ref.d.ts +6 -0
  48. package/dist/scope/vbrief-ref.js +21 -2
  49. package/dist/session/index.d.ts +1 -0
  50. package/dist/session/index.js +1 -0
  51. package/dist/session/posture.d.ts +50 -0
  52. package/dist/session/posture.js +152 -0
  53. package/dist/session/ritual-sentinel.d.ts +2 -0
  54. package/dist/session/ritual-sentinel.js +3 -0
  55. package/dist/session/session-start.d.ts +8 -0
  56. package/dist/session/session-start.js +43 -0
  57. package/dist/session/verify-session-ritual.d.ts +6 -0
  58. package/dist/session/verify-session-ritual.js +67 -4
  59. package/dist/triage/bootstrap/gitignore.js +8 -4
  60. package/dist/triage/cache-path.js +16 -3
  61. package/dist/triage/welcome/writers.js +2 -0
  62. package/dist/ts-check-lane/index.d.ts +1 -1
  63. package/dist/ts-check-lane/index.js +1 -1
  64. package/dist/ts-check-lane/run-lane.d.ts +5 -0
  65. package/dist/ts-check-lane/run-lane.js +15 -0
  66. package/dist/vbrief-build/constants.d.ts +3 -3
  67. package/dist/vbrief-build/constants.js +4 -3
  68. package/dist/vbrief-build/index.d.ts +1 -1
  69. package/dist/vbrief-build/index.js +1 -1
  70. package/dist/verify-env/command-spawn.d.ts +24 -0
  71. package/dist/verify-env/command-spawn.js +78 -0
  72. package/dist/verify-env/index.d.ts +1 -0
  73. package/dist/verify-env/index.js +1 -0
  74. package/package.json +7 -3
@@ -1,10 +1,11 @@
1
1
  import { appendFileSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
2
2
  import { dirname } from "node:path";
3
3
  import { readCorePackageVersion } from "../engine-version.js";
4
- import { resolveEvalPath } from "../layout/resolve.js";
4
+ import { helpedMetricsHistoryPath } from "../metrics/resolve-metrics-home.js";
5
5
  import { scanVbrief } from "../vbrief-validate/conformance.js";
6
6
  import { validateVbriefSchema } from "../vbrief-validate/schema.js";
7
- export const CRUD_METRICS_HISTORY_REL = "results/crud-metrics.jsonl";
7
+ /** Relative path under the resolved metrics home for helped / value telemetry (#2545). */
8
+ export const CRUD_METRICS_HISTORY_REL = "helped/crud-metrics.jsonl";
8
9
  /** Whole-file rewrites change at least half of the bytes or re-serialize with high drift. */
9
10
  export const BYTE_DIFF_WHOLE_FILE_THRESHOLD = 0.5;
10
11
  function isRecord(value) {
@@ -85,16 +86,19 @@ function sanitizeInlineMessage(message) {
85
86
  function ensureParentDir(path) {
86
87
  mkdirSync(dirname(path), { recursive: true });
87
88
  }
88
- /** Absolute path to the versioned CRUD metrics ledger (#1703 Tier 1). */
89
+ /** Absolute path to the versioned CRUD metrics ledger (#1703 Tier 1 / #2545). */
89
90
  export function crudMetricsHistoryPath(projectRoot) {
90
- return resolveEvalPath(projectRoot, CRUD_METRICS_HISTORY_REL);
91
+ return helpedMetricsHistoryPath(projectRoot);
91
92
  }
92
- /** Append CRUD operation metrics to the versioned ledger (#1703 Tier 1). */
93
+ /** Append CRUD operation metrics to the versioned ledger (#1703 Tier 1 / #2545). */
93
94
  export function persistCrudMetrics(projectRoot, metrics) {
94
95
  if (metrics.length === 0) {
95
96
  return;
96
97
  }
97
98
  const path = crudMetricsHistoryPath(projectRoot);
99
+ if (path === null) {
100
+ return;
101
+ }
98
102
  mkdirSync(dirname(path), { recursive: true });
99
103
  for (const metric of metrics) {
100
104
  appendFileSync(path, `${JSON.stringify(metric)}\n`, "utf8");
@@ -1,5 +1,6 @@
1
1
  export declare const HEALTH_SCHEMA_VERSION: 1;
2
- export declare const HEALTH_HISTORY_REL = "results/health-history.jsonl";
2
+ /** Relative path under the resolved metrics home for eval:health history (#2545). */
3
+ export declare const HEALTH_HISTORY_REL = "health/health-history.jsonl";
3
4
  /** One static Tier-0 gate probe aggregated into the health score. */
4
5
  export interface GateProbeResult {
5
6
  readonly id: string;
@@ -37,15 +38,15 @@ export interface EvaluateHealthResult {
37
38
  readonly report: HealthReport | null;
38
39
  readonly message: string;
39
40
  }
40
- /** Absolute path to the versioned health history ledger. */
41
- export declare function healthHistoryPath(projectRoot: string): string;
41
+ /** Absolute path to the versioned health history ledger (#2545). */
42
+ export declare function healthHistoryPath(projectRoot: string): string | null;
42
43
  /** Detect the canonical wipCap unsatisfiable-nudge contradiction (#1694). */
43
44
  export declare function detectWipCapUnsatisfiableNudge(projectRoot: string): ContradictionEvidence | null;
44
45
  /** Run all registered contradictory-gate detectors. */
45
46
  export declare function detectContradictoryGates(projectRoot: string): ContradictionEvidence[];
46
47
  /** Compute a 0-100 score from gate pass rate minus contradiction penalty. */
47
48
  export declare function computeHealthScore(gates: readonly GateProbeResult[], contradictions: readonly ContradictionEvidence[]): number;
48
- /** Append one health run to the versioned ledger (#1703 Tier 0). */
49
+ /** Append one health run to the versioned ledger (#1703 Tier 0 / #2545). */
49
50
  export declare function persistHealthRun(projectRoot: string, report: HealthReport): void;
50
51
  /** Aggregate Tier-0 static gates into a versioned framework health score (#1703). */
51
52
  export declare function evaluateHealth(options?: EvaluateHealthOptions): EvaluateHealthResult;
@@ -3,7 +3,8 @@ import { dirname, resolve } from "node:path";
3
3
  import { agentsRefreshPlan } from "../doctor/agents-md.js";
4
4
  import { evaluate as evaluateEncoding } from "../encoding/evaluate.js";
5
5
  import { readCorePackageVersion } from "../engine-version.js";
6
- import { resolveEvalPath, resolveProjectDefinitionPath } from "../layout/resolve.js";
6
+ import { resolveProjectDefinitionPath } from "../layout/resolve.js";
7
+ import { healthMetricsHistoryPath } from "../metrics/resolve-metrics-home.js";
7
8
  import { readPlanPolicy } from "../policy/plan-extensions.js";
8
9
  import { classifyOnboarding, detectPriorState } from "../triage/welcome/prior-state.js";
9
10
  import { validateLinks } from "../validate-content/index.js";
@@ -11,7 +12,8 @@ import { evaluateConformance } from "../vbrief-validate/conformance.js";
11
12
  import { validateWipCapOnPlan } from "../vbrief-validate/plan-hooks.js";
12
13
  import { evaluateContentManifest } from "../verify-source/content-manifest.js";
13
14
  export const HEALTH_SCHEMA_VERSION = 1;
14
- export const HEALTH_HISTORY_REL = "results/health-history.jsonl";
15
+ /** Relative path under the resolved metrics home for eval:health history (#2545). */
16
+ export const HEALTH_HISTORY_REL = "health/health-history.jsonl";
15
17
  function sanitizeOneLine(value) {
16
18
  return value.replace(/\r?\n/g, " ");
17
19
  }
@@ -99,9 +101,9 @@ function probeContentManifest(projectRoot) {
99
101
  detail: result.code === 0 ? undefined : result.message.split("\n")[0],
100
102
  };
101
103
  }
102
- /** Absolute path to the versioned health history ledger. */
104
+ /** Absolute path to the versioned health history ledger (#2545). */
103
105
  export function healthHistoryPath(projectRoot) {
104
- return resolveEvalPath(projectRoot, HEALTH_HISTORY_REL);
106
+ return healthMetricsHistoryPath(projectRoot);
105
107
  }
106
108
  /** Detect the canonical wipCap unsatisfiable-nudge contradiction (#1694). */
107
109
  export function detectWipCapUnsatisfiableNudge(projectRoot) {
@@ -154,9 +156,12 @@ export function computeHealthScore(gates, contradictions) {
154
156
  const penalty = contradictions.length * 15;
155
157
  return Math.max(0, base - penalty);
156
158
  }
157
- /** Append one health run to the versioned ledger (#1703 Tier 0). */
159
+ /** Append one health run to the versioned ledger (#1703 Tier 0 / #2545). */
158
160
  export function persistHealthRun(projectRoot, report) {
159
161
  const path = healthHistoryPath(projectRoot);
162
+ if (path === null) {
163
+ return;
164
+ }
160
165
  mkdirSync(dirname(path), { recursive: true });
161
166
  appendFileSync(path, `${JSON.stringify(report)}\n`, "utf8");
162
167
  }
@@ -223,17 +228,20 @@ export function evaluateHealth(options = {}) {
223
228
  contradictions,
224
229
  };
225
230
  if (persist) {
226
- try {
227
- persistHealthRun(projectRoot, report);
228
- }
229
- catch (err) {
230
- const persistError = `eval:health: failed to persist health history: ${String(err)}`;
231
- const healthy = score === 100 && contradictions.length === 0;
232
- return {
233
- code: healthy ? 0 : 1,
234
- report,
235
- message: `${formatHumanReport(report)}\n${persistError}`,
236
- };
231
+ const ledgerPath = healthHistoryPath(projectRoot);
232
+ if (ledgerPath !== null) {
233
+ try {
234
+ persistHealthRun(projectRoot, report);
235
+ }
236
+ catch (err) {
237
+ const persistError = `eval:health: failed to persist health history: ${String(err)}`;
238
+ const healthy = score === 100 && contradictions.length === 0;
239
+ return {
240
+ code: healthy ? 0 : 1,
241
+ report,
242
+ message: `${formatHumanReport(report)}\n${persistError}`,
243
+ };
244
+ }
237
245
  }
238
246
  }
239
247
  const healthy = score === 100 && contradictions.length === 0;
@@ -28,14 +28,8 @@ function parseJsonObjectOrNull(text) {
28
28
  return null;
29
29
  }
30
30
  function readHealthHistoryRows(projectRoot) {
31
- let path;
32
- try {
33
- path = healthHistoryPath(projectRoot);
34
- }
35
- catch {
36
- return []; // No xbrief/ layout; no health history.
37
- }
38
- if (!existsSync(path)) {
31
+ const path = healthHistoryPath(projectRoot);
32
+ if (path === null || !existsSync(path)) {
39
33
  return [];
40
34
  }
41
35
  let text;
package/dist/index.d.ts CHANGED
@@ -25,6 +25,7 @@ export * as intake from "./intake/index.js";
25
25
  export * as layout from "./layout/index.js";
26
26
  export * as legacyBridge from "./legacy-bridge/index.js";
27
27
  export * as lifecycle from "./lifecycle/index.js";
28
+ export * as metrics from "./metrics/index.js";
28
29
  export * as orchestration from "./orchestration/index.js";
29
30
  export * as packs from "./packs/index.js";
30
31
  export * as platform from "./platform/index.js";
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ export * as intake from "./intake/index.js";
25
25
  export * as layout from "./layout/index.js";
26
26
  export * as legacyBridge from "./legacy-bridge/index.js";
27
27
  export * as lifecycle from "./lifecycle/index.js";
28
+ export * as metrics from "./metrics/index.js";
28
29
  export * as orchestration from "./orchestration/index.js";
29
30
  export * as packs from "./packs/index.js";
30
31
  export * as platform from "./platform/index.js";
@@ -10,12 +10,19 @@ import { mkdir, readdir, rm, stat, writeFile } from "node:fs/promises";
10
10
  import { platform } from "node:os";
11
11
  import { dirname, join, relative } from "node:path";
12
12
  import { copyTree } from "../deposit/copy-tree.js";
13
+ import { assertProjectionContained, ProjectionContainmentError, } from "../fs/projection-containment.js";
13
14
  import { agentsRefreshPlan } from "../platform/agents-md.js";
14
15
  import { MIGRATED_ARTIFACT_DIR } from "../xbrief-migrate/constants.js";
15
16
  import { CANONICAL_INSTALL_ROOT } from "./constants.js";
16
17
  import { installerManagedGuardEre } from "./hygiene.js";
17
18
  export { CANONICAL_INSTALL_ROOT };
18
19
  export const CORE_GLOB = ".deft/core/**";
20
+ /** Refuse init/update projection writes that escape via repo-controlled symlinks (#2446). */
21
+ function projectionTarget(projectDir, ...relSegments) {
22
+ const target = join(projectDir, ...relSegments);
23
+ assertProjectionContained(projectDir, target);
24
+ return target;
25
+ }
19
26
  const CODEQL_CONFIG_REL = ".github/codeql/codeql-config.yml";
20
27
  const CORE_GUARD_WORKFLOW_REL = ".github/workflows/deft-core-guard.yml";
21
28
  const FRAMEWORK_SELF_TEST_REL = ".deft/core/tests";
@@ -232,7 +239,7 @@ export const PIN_DEPENDENCY_NAME = "@deftai/directive";
232
239
  */
233
240
  export function ensurePackageJsonPin(projectDir, version, io) {
234
241
  const pinVersion = version.trim().replace(/^v/i, "");
235
- const path = join(projectDir, "package.json");
242
+ const path = projectionTarget(projectDir, "package.json");
236
243
  const existed = existsSync(path);
237
244
  let pkg = {};
238
245
  if (existed) {
@@ -284,7 +291,7 @@ export function writeAgentsMd(projectDir, deftDir, io) {
284
291
  if (typeof newContent !== "string") {
285
292
  throw new Error("AGENTS.md render produced no content");
286
293
  }
287
- const path = join(projectDir, "AGENTS.md");
294
+ const path = projectionTarget(projectDir, "AGENTS.md");
288
295
  writeFileSync(path, newContent, "utf8");
289
296
  if (state === "absent") {
290
297
  io.printf("AGENTS.md created.\n");
@@ -294,9 +301,9 @@ export function writeAgentsMd(projectDir, deftDir, io) {
294
301
  }
295
302
  return true;
296
303
  }
297
- async function ensureVbriefLifecycleDirs(consumerVbrief) {
304
+ async function ensureVbriefLifecycleDirs(projectDir) {
298
305
  for (const sub of VBRIEF_LIFECYCLE_DIRS) {
299
- const dir = join(consumerVbrief, sub);
306
+ const dir = projectionTarget(projectDir, MIGRATED_ARTIFACT_DIR, sub);
300
307
  await mkdir(dir, { recursive: true, mode: 0o755 });
301
308
  const gitkeep = join(dir, ".gitkeep");
302
309
  try {
@@ -323,9 +330,9 @@ function vbriefLifecycleDirsPresent(consumerVbrief) {
323
330
  });
324
331
  }
325
332
  export async function writeConsumerVbrief(projectDir, deftDir, io) {
326
- const consumerVbrief = join(projectDir, MIGRATED_ARTIFACT_DIR);
327
- const schemasDst = join(consumerVbrief, "schemas");
328
- const vbriefMdDst = join(consumerVbrief, "vbrief.md");
333
+ const consumerVbrief = projectionTarget(projectDir, MIGRATED_ARTIFACT_DIR);
334
+ const schemasDst = projectionTarget(projectDir, MIGRATED_ARTIFACT_DIR, "schemas");
335
+ const vbriefMdDst = projectionTarget(projectDir, MIGRATED_ARTIFACT_DIR, "vbrief.md");
329
336
  const schemasPresent = existsSync(schemasDst) && statSync(schemasDst).isDirectory();
330
337
  const vbriefMdPresent = existsSync(vbriefMdDst) && statSync(vbriefMdDst).isFile();
331
338
  const lifecyclePresent = vbriefLifecycleDirsPresent(consumerVbrief);
@@ -352,20 +359,21 @@ export async function writeConsumerVbrief(projectDir, deftDir, io) {
352
359
  writeFileSync(vbriefMdDst, VBRIEF_README_BODY, "utf8");
353
360
  }
354
361
  }
355
- await ensureVbriefLifecycleDirs(consumerVbrief);
362
+ await ensureVbriefLifecycleDirs(projectDir);
356
363
  io.printf("vbrief/ deposited at project root (schemas + vbrief.md + lifecycle dirs).\n");
357
364
  return true;
358
365
  }
359
366
  export function writeAgentsSkills(projectDir, io) {
367
+ projectionTarget(projectDir, ".agents");
360
368
  const allExist = AGENTS_SKILLS.every((skill) => existsSync(join(projectDir, ".agents", "skills", skill.dir, "SKILL.md")));
361
369
  if (allExist) {
362
370
  io.printf(".agents/skills/ already present — skipping.\n");
363
371
  return false;
364
372
  }
365
373
  for (const skill of AGENTS_SKILLS) {
366
- const dir = join(projectDir, ".agents", "skills", skill.dir);
374
+ const dir = projectionTarget(projectDir, ".agents", "skills", skill.dir);
367
375
  mkdirSync(dir, { recursive: true });
368
- const path = join(dir, "SKILL.md");
376
+ const path = projectionTarget(projectDir, ".agents", "skills", skill.dir, "SKILL.md");
369
377
  if (existsSync(path))
370
378
  continue;
371
379
  writeFileSync(path, skill.content, "utf8");
@@ -412,7 +420,7 @@ function insertDeftIncludeAfterIncludesLine(content) {
412
420
  return { content, ok: false };
413
421
  }
414
422
  export function ensureTaskfile(projectDir, io) {
415
- const path = join(projectDir, "Taskfile.yml");
423
+ const path = projectionTarget(projectDir, "Taskfile.yml");
416
424
  let existing = "";
417
425
  if (existsSync(path)) {
418
426
  existing = readFileSync(path, "utf8");
@@ -466,7 +474,7 @@ export function writeConsumerGitHooks(projectDir, deftDir, io, seams = {}) {
466
474
  io.printf(`git hooks source ${srcDir} absent — skipping hook wiring.\n`);
467
475
  return false;
468
476
  }
469
- const dstDir = join(projectDir, ".githooks");
477
+ const dstDir = projectionTarget(projectDir, ".githooks");
470
478
  mkdirSync(dstDir, { recursive: true });
471
479
  let filesDeposited = false;
472
480
  for (const name of [...HOOK_FILENAMES, ...HOOK_SUPPORT_FILENAMES]) {
@@ -474,7 +482,7 @@ export function writeConsumerGitHooks(projectDir, deftDir, io, seams = {}) {
474
482
  if (!existsSync(src))
475
483
  continue;
476
484
  const data = readFileSync(src);
477
- const dst = join(dstDir, name);
485
+ const dst = projectionTarget(projectDir, ".githooks", name);
478
486
  const existing = existsSync(dst) ? readFileSync(dst) : null;
479
487
  const isHookScript = HOOK_FILENAMES.includes(name);
480
488
  if (!existing?.equals(data)) {
@@ -656,7 +664,7 @@ function coreGuardWorkflowContent() {
656
664
  ' echo "OK: no mixed framework + app changes."\n');
657
665
  }
658
666
  export function ensureGitattributes(projectDir, io) {
659
- const path = join(projectDir, ".gitattributes");
667
+ const path = projectionTarget(projectDir, ".gitattributes");
660
668
  const existing = existsSync(path) ? readFileSync(path, "utf8") : "";
661
669
  const present = new Set(existing.split("\n").map((line) => line.trim()));
662
670
  const additions = CORE_GITATTRIBUTES_LINES.filter((line) => !present.has(line));
@@ -691,7 +699,7 @@ function appendGreptilePattern(patterns, glob) {
691
699
  return `${patterns}\n${glob}`;
692
700
  }
693
701
  export function ensureGreptileIgnore(projectDir, io) {
694
- const path = join(projectDir, "greptile.json");
702
+ const path = projectionTarget(projectDir, "greptile.json");
695
703
  const fileExisted = existsSync(path);
696
704
  let raw = fileExisted ? readFileSync(path, "utf8") : "";
697
705
  if (!raw.trim())
@@ -766,7 +774,7 @@ function insertCodeqlPathsIgnore(content, glob) {
766
774
  return { content, ok: false };
767
775
  }
768
776
  export function ensureCodeqlPathsIgnore(projectDir, io) {
769
- const path = join(projectDir, CODEQL_CONFIG_REL);
777
+ const path = projectionTarget(projectDir, CODEQL_CONFIG_REL);
770
778
  if (!existsSync(path)) {
771
779
  mkdirSync(dirname(path), { recursive: true });
772
780
  writeFileSync(path, codeqlConfigDefault(), "utf8");
@@ -787,7 +795,7 @@ export function ensureCodeqlPathsIgnore(projectDir, io) {
787
795
  return true;
788
796
  }
789
797
  export function ensureCoreGuardWorkflow(projectDir, io) {
790
- const path = join(projectDir, CORE_GUARD_WORKFLOW_REL);
798
+ const path = projectionTarget(projectDir, CORE_GUARD_WORKFLOW_REL);
791
799
  const desired = coreGuardWorkflowContent();
792
800
  if (existsSync(path)) {
793
801
  const existing = readFileSync(path, "utf8");
@@ -866,6 +874,9 @@ export async function depositNeutralization(projectDir, io, options = {}) {
866
874
  await step();
867
875
  }
868
876
  catch (cause) {
877
+ if (cause instanceof ProjectionContainmentError) {
878
+ throw cause;
879
+ }
869
880
  io.printf(`Warning: neutralization step failed: ${String(cause)}\n`);
870
881
  }
871
882
  }
@@ -8,7 +8,7 @@ import { resolveProjectRoot } from "../scope/project-context.js";
8
8
  import { resolveProjectRepo } from "../slice/project-context.js";
9
9
  import { slugify, TODAY } from "../vbrief-build/build.js";
10
10
  import { EMITTED_VBRIEF_VERSION } from "../vbrief-build/constants.js";
11
- import { LEGACY_ARTIFACT_SUFFIX, LEGACY_INFO_ROOT_KEY, LEGACY_VBRIEF_VERSION, MIGRATED_ARTIFACT_DIR, MIGRATED_ARTIFACT_SUFFIX, MIGRATED_INFO_ROOT_KEY, VBRIEF_VERSION, } from "../xbrief-migrate/constants.js";
11
+ import { LEGACY_ARTIFACT_SUFFIX, LEGACY_INFO_ROOT_KEY, MIGRATED_ARTIFACT_DIR, MIGRATED_ARTIFACT_SUFFIX, MIGRATED_INFO_ROOT_KEY, VBRIEF_VERSION, } from "../xbrief-migrate/constants.js";
12
12
  import { findAcHeading, parseCheckboxItems, parseListItems, sliceAcSection, stripCodeBlocks, stripFencedCodeBlocks, } from "./markdown-scanners.js";
13
13
  import { detectRepo, extractReferencesFromVbrief, fetchOpenIssues, GITHUB_ISSUE_REF_TYPES, LIFECYCLE_FOLDERS, parseIssueNumber, } from "./reconcile-issues.js";
14
14
  export const INGEST_STATUSES = ["proposed", "pending", "active"];
@@ -78,7 +78,7 @@ function resolveIngestEmissionLayout(vbriefDir) {
78
78
  return {
79
79
  artifactSuffix: LEGACY_ARTIFACT_SUFFIX,
80
80
  infoRootKey: LEGACY_INFO_ROOT_KEY,
81
- infoVersion: LEGACY_VBRIEF_VERSION,
81
+ infoVersion: EMITTED_VBRIEF_VERSION,
82
82
  };
83
83
  }
84
84
  return {
@@ -300,11 +300,25 @@ export function issueCommentThread(issue) {
300
300
  export function issueCommentsAlreadyFetched(issue) {
301
301
  return Object.hasOwn(issue, ISSUE_COMMENT_THREAD_KEY);
302
302
  }
303
+ /**
304
+ * Run quarantine `scan()` on untrusted ingest text (#2447). Fail closed on a
305
+ * credential hard-fail; return the fenced/quarantined transform for soft signals.
306
+ */
307
+ function scanUntrustedIngestText(issueNumber, text) {
308
+ const scanResult = scan(text);
309
+ const hardFails = scanResult.flags.filter((f) => f.severity === "hard-fail");
310
+ if (hardFails.length > 0) {
311
+ throw new ScannerHardFailError(issueNumber, scanResult.flags);
312
+ }
313
+ return scanResult.transformed_content;
314
+ }
303
315
  export function buildIssueVbrief(issue, status, repoUrl, options = {}) {
304
316
  const number = Number(issue.number);
305
- const title = (typeof issue.title === "string" && issue.title.length > 0
317
+ const titleRaw = (typeof issue.title === "string" && issue.title.length > 0
306
318
  ? issue.title
307
319
  : `Issue #${number}`) || `Issue #${number}`;
320
+ // #2447: quarantine-scan issue title before it lands in agent-authoritative fields.
321
+ const title = scanUntrustedIngestText(number, titleRaw);
308
322
  const url = (typeof issue.url === "string" && issue.url.length > 0 ? issue.url : "") ||
309
323
  (repoUrl.length > 0 ? `${repoUrl}/issues/${number}` : "");
310
324
  const bodyRaw = issue.body;
@@ -345,7 +359,12 @@ export function buildIssueVbrief(issue, status, repoUrl, options = {}) {
345
359
  if (labelNames.length > 0) {
346
360
  narratives.Labels = labelNames.join(", ");
347
361
  }
348
- const planItems = bodyStr.length > 0 ? extractPlanItems(bodyStr) : [];
362
+ const planItemsRaw = bodyStr.length > 0 ? extractPlanItems(bodyStr) : [];
363
+ // #2447: scan each derived plan-item title (empty-body issues still scan title above).
364
+ const planItems = planItemsRaw.map((item) => ({
365
+ ...item,
366
+ title: scanUntrustedIngestText(number, String(item.title ?? "")),
367
+ }));
349
368
  const plan = {
350
369
  title,
351
370
  status: planStatus,
@@ -0,0 +1,2 @@
1
+ export * from "./resolve-metrics-home.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,2 @@
1
+ export * from "./resolve-metrics-home.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Shared metrics home resolver (#2545).
3
+ *
4
+ * Helped (value / CRUD) and health (eval:health) append logs are machine-local
5
+ * telemetry — not project source. They resolve outside the git worktree via an
6
+ * explicit ladder shared with USER.md platform discovery (#2271 / #2544):
7
+ *
8
+ * 1. `DEFT_METRICS_HOME` or `DEFT_EVAL_HOME` — explicit override (CI / headless)
9
+ * 2. `<projectRoot>/.deft/metrics/` — workspace-local opt-in when
10
+ * `DEFT_METRICS_PROJECT_LOCAL=1`
11
+ * 3. Platform user-data (`%APPDATA%\deft\metrics` / `~/.config/deft/metrics`)
12
+ * 4. Soft-disable when the resolved root is not writable — never fall back to
13
+ * `xbrief/.eval/results/`.
14
+ */
15
+ export declare const METRICS_DISABLED_DIAGNOSTIC = "metrics persistence disabled";
16
+ export declare const DEFT_METRICS_HOME_ENV = "DEFT_METRICS_HOME";
17
+ export declare const DEFT_EVAL_HOME_ENV = "DEFT_EVAL_HOME";
18
+ export declare const DEFT_METRICS_PROJECT_LOCAL_ENV = "DEFT_METRICS_PROJECT_LOCAL";
19
+ export declare const PROJECT_LOCAL_METRICS_DIR = ".deft/metrics";
20
+ export declare const HELPED_METRICS_FILENAME = "crud-metrics.jsonl";
21
+ export declare const HEALTH_METRICS_FILENAME = "health-history.jsonl";
22
+ export type MetricsHomeRung = "env-override" | "project-local" | "platform-user-data" | "disabled";
23
+ export interface ResolveMetricsHomeResult {
24
+ /** Absolute metrics root, or null when persistence is disabled. */
25
+ readonly root: string | null;
26
+ readonly rung: MetricsHomeRung;
27
+ /** False when no writable metrics root could be resolved. */
28
+ readonly enabled: boolean;
29
+ readonly diagnostic: string;
30
+ }
31
+ export interface ResolveMetricsHomeOptions {
32
+ readonly projectRoot?: string;
33
+ readonly env?: NodeJS.ProcessEnv;
34
+ readonly platform?: NodeJS.Platform;
35
+ readonly homeDir?: string;
36
+ /** Writable probe. Defaults to mkdir + W_OK access check. */
37
+ readonly probeWritable?: (path: string) => boolean;
38
+ }
39
+ /**
40
+ * Resolve the platform user-data metrics directory (`%APPDATA%\deft\metrics` /
41
+ * `~/.config/deft/metrics`).
42
+ */
43
+ export declare function platformMetricsDir(platform: NodeJS.Platform, env: NodeJS.ProcessEnv, homeDir: string): string;
44
+ /** Resolve the metrics home root across the first-hit-wins ladder (#2545). */
45
+ export declare function resolveMetricsHome(options?: ResolveMetricsHomeOptions): ResolveMetricsHomeResult;
46
+ /** Absolute path to the helped / value metrics ledger (`helped/crud-metrics.jsonl`). */
47
+ export declare function helpedMetricsHistoryPath(projectRoot: string, options?: ResolveMetricsHomeOptions): string | null;
48
+ /** Absolute path to the health metrics ledger (`health/health-history.jsonl`). */
49
+ export declare function healthMetricsHistoryPath(projectRoot: string, options?: ResolveMetricsHomeOptions): string | null;
50
+ //# sourceMappingURL=resolve-metrics-home.d.ts.map
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Shared metrics home resolver (#2545).
3
+ *
4
+ * Helped (value / CRUD) and health (eval:health) append logs are machine-local
5
+ * telemetry — not project source. They resolve outside the git worktree via an
6
+ * explicit ladder shared with USER.md platform discovery (#2271 / #2544):
7
+ *
8
+ * 1. `DEFT_METRICS_HOME` or `DEFT_EVAL_HOME` — explicit override (CI / headless)
9
+ * 2. `<projectRoot>/.deft/metrics/` — workspace-local opt-in when
10
+ * `DEFT_METRICS_PROJECT_LOCAL=1`
11
+ * 3. Platform user-data (`%APPDATA%\deft\metrics` / `~/.config/deft/metrics`)
12
+ * 4. Soft-disable when the resolved root is not writable — never fall back to
13
+ * `xbrief/.eval/results/`.
14
+ */
15
+ import { accessSync, constants, mkdirSync } from "node:fs";
16
+ import { homedir } from "node:os";
17
+ import { join, resolve } from "node:path";
18
+ import { platformUserConfigDir } from "../user-config/resolve-user-md.js";
19
+ export const METRICS_DISABLED_DIAGNOSTIC = "metrics persistence disabled";
20
+ export const DEFT_METRICS_HOME_ENV = "DEFT_METRICS_HOME";
21
+ export const DEFT_EVAL_HOME_ENV = "DEFT_EVAL_HOME";
22
+ export const DEFT_METRICS_PROJECT_LOCAL_ENV = "DEFT_METRICS_PROJECT_LOCAL";
23
+ export const PROJECT_LOCAL_METRICS_DIR = ".deft/metrics";
24
+ export const HELPED_METRICS_FILENAME = "crud-metrics.jsonl";
25
+ export const HEALTH_METRICS_FILENAME = "health-history.jsonl";
26
+ function defaultProbeWritable(path) {
27
+ try {
28
+ mkdirSync(path, { recursive: true });
29
+ accessSync(path, constants.W_OK);
30
+ return true;
31
+ }
32
+ catch {
33
+ return false;
34
+ }
35
+ }
36
+ function metricsEnvOverride(env) {
37
+ const primary = env[DEFT_METRICS_HOME_ENV]?.trim();
38
+ if (primary) {
39
+ return primary;
40
+ }
41
+ const legacy = env[DEFT_EVAL_HOME_ENV]?.trim();
42
+ return legacy || null;
43
+ }
44
+ /**
45
+ * Resolve the platform user-data metrics directory (`%APPDATA%\deft\metrics` /
46
+ * `~/.config/deft/metrics`).
47
+ */
48
+ export function platformMetricsDir(platform, env, homeDir) {
49
+ return join(platformUserConfigDir(platform, env, homeDir), "metrics");
50
+ }
51
+ /** Resolve the metrics home root across the first-hit-wins ladder (#2545). */
52
+ export function resolveMetricsHome(options = {}) {
53
+ const env = options.env ?? process.env;
54
+ const platform = options.platform ?? process.platform;
55
+ const homeDir = options.homeDir ?? homedir();
56
+ const projectRoot = options.projectRoot ?? process.cwd();
57
+ const probeWritable = options.probeWritable ?? defaultProbeWritable;
58
+ const override = metricsEnvOverride(env);
59
+ if (override) {
60
+ const root = resolve(override);
61
+ if (probeWritable(root)) {
62
+ return {
63
+ root,
64
+ rung: "env-override",
65
+ enabled: true,
66
+ diagnostic: `metrics home resolved from ${DEFT_METRICS_HOME_ENV}/${DEFT_EVAL_HOME_ENV}: ${root}`,
67
+ };
68
+ }
69
+ return {
70
+ root: null,
71
+ rung: "disabled",
72
+ enabled: false,
73
+ diagnostic: `${METRICS_DISABLED_DIAGNOSTIC} (${DEFT_METRICS_HOME_ENV} not writable: ${root})`,
74
+ };
75
+ }
76
+ if (env[DEFT_METRICS_PROJECT_LOCAL_ENV] === "1") {
77
+ const root = resolve(join(projectRoot, PROJECT_LOCAL_METRICS_DIR));
78
+ if (probeWritable(root)) {
79
+ return {
80
+ root,
81
+ rung: "project-local",
82
+ enabled: true,
83
+ diagnostic: `metrics home resolved from workspace-local opt-in: ${root}`,
84
+ };
85
+ }
86
+ return {
87
+ root: null,
88
+ rung: "disabled",
89
+ enabled: false,
90
+ diagnostic: `${METRICS_DISABLED_DIAGNOSTIC} (workspace-local metrics dir not writable: ${root})`,
91
+ };
92
+ }
93
+ const root = platformMetricsDir(platform, env, homeDir);
94
+ if (probeWritable(root)) {
95
+ return {
96
+ root,
97
+ rung: "platform-user-data",
98
+ enabled: true,
99
+ diagnostic: `metrics home resolved from platform user-data: ${root}`,
100
+ };
101
+ }
102
+ return {
103
+ root: null,
104
+ rung: "disabled",
105
+ enabled: false,
106
+ diagnostic: `${METRICS_DISABLED_DIAGNOSTIC} (platform user-data not writable: ${root})`,
107
+ };
108
+ }
109
+ /** Absolute path to the helped / value metrics ledger (`helped/crud-metrics.jsonl`). */
110
+ export function helpedMetricsHistoryPath(projectRoot, options = {}) {
111
+ const home = resolveMetricsHome({ ...options, projectRoot });
112
+ if (!home.enabled || home.root === null) {
113
+ return null;
114
+ }
115
+ return join(home.root, "helped", HELPED_METRICS_FILENAME);
116
+ }
117
+ /** Absolute path to the health metrics ledger (`health/health-history.jsonl`). */
118
+ export function healthMetricsHistoryPath(projectRoot, options = {}) {
119
+ const home = resolveMetricsHome({ ...options, projectRoot });
120
+ if (!home.enabled || home.root === null) {
121
+ return null;
122
+ }
123
+ return join(home.root, "health", HEALTH_METRICS_FILENAME);
124
+ }
125
+ //# sourceMappingURL=resolve-metrics-home.js.map
@@ -0,0 +1,12 @@
1
+ export type OpenPackageTierName = "daily-core" | "standard" | "advanced";
2
+ export interface OpenPackageTierManifest {
3
+ readonly defaultInstallTier: OpenPackageTierName;
4
+ readonly tiers: Record<OpenPackageTierName, {
5
+ skills: string[];
6
+ }>;
7
+ }
8
+ /** Default OpenPackage install tier from deft-tiers.json (#2494). */
9
+ export declare function getOpenPackageDefaultInstallTier(repoRoot: string): OpenPackageTierName;
10
+ /** Skill names for an OpenPackage tier selection (`all` = every mapped skill). */
11
+ export declare function resolveOpenPackageTierSkills(repoRoot: string, tier: OpenPackageTierName | "all"): string[];
12
+ //# sourceMappingURL=openpackage-tiers.d.ts.map
@@ -0,0 +1,42 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ const TIER_NAMES = ["daily-core", "standard", "advanced"];
4
+ function isOpenPackageTierName(value) {
5
+ return TIER_NAMES.includes(value);
6
+ }
7
+ function loadOpenPackageTierManifest(repoRoot) {
8
+ const path = join(repoRoot, "packaging", "openpackage", "deft-tiers.json");
9
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
10
+ if (parsed === null || typeof parsed !== "object" || !parsed.tiers) {
11
+ throw new Error("deft-tiers.json must be an object with a tiers field");
12
+ }
13
+ for (const tier of TIER_NAMES) {
14
+ const block = parsed.tiers[tier];
15
+ if (!block || !Array.isArray(block.skills)) {
16
+ throw new Error(`deft-tiers.json missing tiers.${tier}.skills`);
17
+ }
18
+ }
19
+ const defaultInstallTier = parsed.defaultInstallTier ?? "daily-core";
20
+ if (!isOpenPackageTierName(defaultInstallTier)) {
21
+ throw new Error(`deft-tiers.json defaultInstallTier must be one of ${TIER_NAMES.join(", ")}; got ${defaultInstallTier}`);
22
+ }
23
+ return {
24
+ defaultInstallTier,
25
+ tiers: parsed.tiers,
26
+ };
27
+ }
28
+ function resolveTierSkills(manifest, tier) {
29
+ if (tier === "all") {
30
+ return [...new Set(TIER_NAMES.flatMap((name) => manifest.tiers[name].skills))];
31
+ }
32
+ return [...manifest.tiers[tier].skills];
33
+ }
34
+ /** Default OpenPackage install tier from deft-tiers.json (#2494). */
35
+ export function getOpenPackageDefaultInstallTier(repoRoot) {
36
+ return loadOpenPackageTierManifest(repoRoot).defaultInstallTier;
37
+ }
38
+ /** Skill names for an OpenPackage tier selection (`all` = every mapped skill). */
39
+ export function resolveOpenPackageTierSkills(repoRoot, tier) {
40
+ return resolveTierSkills(loadOpenPackageTierManifest(repoRoot), tier);
41
+ }
42
+ //# sourceMappingURL=openpackage-tiers.js.map
@@ -0,0 +1,3 @@
1
+ export { clearPlanSequence, planSequencePath, readPlanSequence, writePlanSequence, } from "./store.js";
2
+ export { advancePlanSequence, type ContinuationResolution, createPlanSequence, EXHAUSTED_FAIL_CLOSED_MESSAGE, EXPLICIT_QUEUE_PHRASES, isExplicitQueueAsk, isPlanFirstPhrase, PLAN_FIRST_PHRASES, PLAN_SEQUENCE_CONTRACT, PLAN_SEQUENCE_FILENAME, type PlanSequence, type PlanSequenceEntry, type PlanSequenceKind, type PlanSequenceVerifyInput, type PlanSequenceVerifyResult, type PlanTargetKind, parsePlanSequence, resolveContinuation, verifyPlanTarget, } from "./types.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,3 @@
1
+ export { clearPlanSequence, planSequencePath, readPlanSequence, writePlanSequence, } from "./store.js";
2
+ export { advancePlanSequence, createPlanSequence, EXHAUSTED_FAIL_CLOSED_MESSAGE, EXPLICIT_QUEUE_PHRASES, isExplicitQueueAsk, isPlanFirstPhrase, PLAN_FIRST_PHRASES, PLAN_SEQUENCE_CONTRACT, PLAN_SEQUENCE_FILENAME, parsePlanSequence, resolveContinuation, verifyPlanTarget, } from "./types.js";
3
+ //# sourceMappingURL=index.js.map