snipara-companion 3.0.8 → 3.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -9750,15 +9750,15 @@ function parseEnvLine(line) {
9750
9750
  return null;
9751
9751
  }
9752
9752
  const withoutExport = withoutWhitespace.startsWith("export ") ? withoutWhitespace.slice("export ".length).trim() : withoutWhitespace;
9753
- const separatorIndex = withoutExport.indexOf("=");
9754
- if (separatorIndex <= 0) {
9753
+ const separatorIndex2 = withoutExport.indexOf("=");
9754
+ if (separatorIndex2 <= 0) {
9755
9755
  return null;
9756
9756
  }
9757
- const key = withoutExport.slice(0, separatorIndex).trim();
9757
+ const key = withoutExport.slice(0, separatorIndex2).trim();
9758
9758
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
9759
9759
  return null;
9760
9760
  }
9761
- const value = parseEnvValue(withoutExport.slice(separatorIndex + 1).trim());
9761
+ const value = parseEnvValue(withoutExport.slice(separatorIndex2 + 1).trim());
9762
9762
  return { key, value };
9763
9763
  }
9764
9764
  function parseEnvValue(rawValue) {
@@ -19612,7 +19612,7 @@ async function agentReadinessAuditCommand(options) {
19612
19612
  var fs21 = __toESM(require("fs"));
19613
19613
  var path20 = __toESM(require("path"));
19614
19614
 
19615
- // ../project-intelligence-contracts/src/index.ts
19615
+ // ../project-intelligence-contracts/src/engineering-lead.ts
19616
19616
  var PROJECT_HEALTH_COCKPIT_STATUSES = ["healthy", "watch", "risk", "unknown"];
19617
19617
  var PROJECT_INTELLIGENCE_ENGINEERING_LEAD_PLAN_VERSION = "project-intelligence-engineering-lead-plan-v0";
19618
19618
  var PROJECT_INTELLIGENCE_ENGINEERING_LEAD_CONTRACT_VERSION = "engineering-lead-contract-v1";
@@ -19686,9 +19686,8 @@ var PROJECT_INTELLIGENCE_ENGINEERING_LEAD_PROOF_VERIFICATION_SOURCES = [
19686
19686
  "manual",
19687
19687
  "unknown"
19688
19688
  ];
19689
- var PROJECT_REALITY_CHECK_VERSION = "project-reality-check-v0";
19690
- var PROJECT_INTENT_LEDGER_VERSION = "intent-ledger-v1";
19691
- var PROJECT_UNKNOWN_REGISTRY_VERSION = "unknown-registry-v1";
19689
+
19690
+ // ../project-intelligence-contracts/src/shared.ts
19692
19691
  var SENSITIVE_SURFACES = [
19693
19692
  {
19694
19693
  key: "billing_atomicity",
@@ -19764,23 +19763,11 @@ function hasPattern(values, patterns) {
19764
19763
  function matchesAnyText(text, patterns) {
19765
19764
  return patterns.some((pattern) => pattern.test(text));
19766
19765
  }
19767
- function findingSeverityScore(severity) {
19768
- if (severity === "blocking") return 0;
19769
- if (severity === "review_required") return 30;
19770
- if (severity === "watch") return 70;
19771
- return 90;
19772
- }
19773
- function resultStatusFromFindings(findings) {
19774
- if (findings.some((finding) => finding.severity === "blocking")) return "blocking";
19775
- if (findings.some((finding) => finding.severity === "review_required")) return "review_required";
19776
- if (findings.some((finding) => finding.severity === "watch")) return "advisory";
19777
- return "pass";
19778
- }
19779
- function makeFindingId(type, key, files) {
19766
+ function makeScopedId(prefix, type, key, files) {
19780
19767
  const compactFiles = files.slice(0, 3).map(
19781
19768
  (file) => file.replace(/[^a-z0-9]+/gi, "-").replace(/^-|-$/g, "").toLowerCase()
19782
19769
  ).filter(Boolean).join("-");
19783
- return ["reality", type, key, compactFiles || "project"].join(":").slice(0, 180);
19770
+ return [prefix, type, key, compactFiles || "project"].join(":").slice(0, 180);
19784
19771
  }
19785
19772
  function decisionMatchesChangedFiles(decision, changedFiles) {
19786
19773
  const anchors = decision.affectedAnchors ?? [];
@@ -19795,7 +19782,15 @@ function decisionText(decision) {
19795
19782
  decision.decision,
19796
19783
  decision.rationale,
19797
19784
  decision.scope,
19798
- ...decision.constraints ?? []
19785
+ decision.owner,
19786
+ ...decision.constraints ?? [],
19787
+ ...decision.antiGoals ?? [],
19788
+ ...decision.rejectedAlternatives ?? [],
19789
+ decision.intent?.goal,
19790
+ decision.intent?.owner,
19791
+ ...decision.intent?.constraints ?? [],
19792
+ ...decision.intent?.antiGoals ?? [],
19793
+ ...decision.intent?.rejectedAlternatives ?? []
19799
19794
  ].filter(Boolean).join(" ");
19800
19795
  }
19801
19796
  function confidenceBand(score) {
@@ -19807,12 +19802,83 @@ function normalizeConfidenceScore(value, fallback) {
19807
19802
  if (typeof value !== "number" || Number.isNaN(value)) return fallback;
19808
19803
  return Math.max(0, Math.min(1, value));
19809
19804
  }
19810
- function intentStatusFromDecision(decision) {
19811
- const status = normalizeText(decision.status);
19812
- if (/contradict|conflict/.test(status)) return "contradicted";
19813
- if (/supersed|replac/.test(status)) return "superseded";
19814
- if (/stale|expired|deprecated/.test(status)) return "stale";
19815
- if (/approved|accepted|active|confirmed/.test(status)) return "approved";
19805
+ function anchorMatchesFile(anchor, file) {
19806
+ const normalizedAnchor = anchor.replace(/^\.\//, "").trim();
19807
+ const normalizedFile = file.replace(/^\.\//, "").trim();
19808
+ return normalizedAnchor.length > 0 && normalizedFile.length > 0 && (normalizedFile.includes(normalizedAnchor) || normalizedAnchor.includes(normalizedFile));
19809
+ }
19810
+ function surfaceFiles(surface, changedFiles) {
19811
+ return changedFiles.filter((file) => surface.patterns.some((pattern) => pattern.test(file)));
19812
+ }
19813
+
19814
+ // ../project-intelligence-contracts/src/intent-ledger.ts
19815
+ var PROJECT_INTENT_LEDGER_VERSION = "intent-ledger-v1";
19816
+ var PROJECT_INTENT_LEDGER_DEFAULT_FRESHNESS_HORIZON_DAYS = 90;
19817
+ var DEFAULT_PROJECT_INTENT_LEDGER_POLICY = {
19818
+ freshnessHorizonDays: PROJECT_INTENT_LEDGER_DEFAULT_FRESHNESS_HORIZON_DAYS
19819
+ };
19820
+ var DECISION_STATUS_ALIASES = {
19821
+ approved: ["approved", "accepted", "active", "confirmed"],
19822
+ review_pending: ["review_pending", "pending_review", "pending", "candidate", "draft"],
19823
+ stale: ["stale", "expired", "deprecated"],
19824
+ superseded: ["superseded", "replaced", "overridden"],
19825
+ contradicted: ["contradicted", "conflicted", "conflict"]
19826
+ };
19827
+ var INTENT_SECTION_ALIASES = {
19828
+ goal: ["goal", "intent", "why", "purpose", "rationale"],
19829
+ constraints: ["constraint", "constraints", "rule", "rules", "must"],
19830
+ antiGoals: ["anti_goal", "anti_goals", "avoid", "forbidden", "do_not"],
19831
+ rejectedAlternatives: [
19832
+ "rejected_alternative",
19833
+ "rejected_alternatives",
19834
+ "rejected",
19835
+ "not_chosen",
19836
+ "alternative_rejected",
19837
+ "alternatives_rejected"
19838
+ ],
19839
+ owner: ["owner", "owners"],
19840
+ freshnessHorizonDays: ["freshness_horizon_days", "horizon_days", "staleness_horizon_days"]
19841
+ };
19842
+ var INTENT_DOCUMENT_KIND_TOKENS = [
19843
+ "adr",
19844
+ "decision",
19845
+ "intent",
19846
+ "rationale",
19847
+ "constraint",
19848
+ "architecture",
19849
+ "runbook"
19850
+ ];
19851
+ function normalizeIdentifier(value) {
19852
+ const normalized = [];
19853
+ let previousWasSeparator = false;
19854
+ for (const char of value?.trim().toLowerCase() ?? "") {
19855
+ const code2 = char.charCodeAt(0);
19856
+ const isAlphaNumeric = code2 >= 48 && code2 <= 57 || code2 >= 97 && code2 <= 122;
19857
+ if (isAlphaNumeric) {
19858
+ normalized.push(char);
19859
+ previousWasSeparator = false;
19860
+ continue;
19861
+ }
19862
+ if (!previousWasSeparator && normalized.length > 0) {
19863
+ normalized.push("_");
19864
+ previousWasSeparator = true;
19865
+ }
19866
+ }
19867
+ if (normalized[normalized.length - 1] === "_") normalized.pop();
19868
+ return normalized.join("");
19869
+ }
19870
+ function statusFromDecisionStatus(decision) {
19871
+ const status = normalizeIdentifier(decision.status);
19872
+ if (!status) return "review_pending";
19873
+ for (const [ledgerStatus, aliases] of Object.entries(DECISION_STATUS_ALIASES)) {
19874
+ if (aliases.includes(status)) return ledgerStatus;
19875
+ }
19876
+ const statusParts = status.split("_").filter(Boolean);
19877
+ for (const [ledgerStatus, aliases] of Object.entries(DECISION_STATUS_ALIASES)) {
19878
+ if (aliases.some((alias) => statusParts.includes(alias))) {
19879
+ return ledgerStatus;
19880
+ }
19881
+ }
19816
19882
  return "review_pending";
19817
19883
  }
19818
19884
  function intentConfidence(score, status, reasonCodes) {
@@ -19823,7 +19889,14 @@ function intentConfidence(score, status, reasonCodes) {
19823
19889
  reasonCodes
19824
19890
  };
19825
19891
  }
19826
- function stalenessFromStatus(status) {
19892
+ function normalizeFreshnessHorizonDays(value) {
19893
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return null;
19894
+ return Math.round(value);
19895
+ }
19896
+ function resolveFreshnessHorizonDays(specificValue, policy) {
19897
+ return normalizeFreshnessHorizonDays(specificValue) ?? normalizeFreshnessHorizonDays(policy.freshnessHorizonDays) ?? null;
19898
+ }
19899
+ function stalenessFromStatus(status, freshnessHorizonDays) {
19827
19900
  if (status === "stale" || status === "superseded") {
19828
19901
  return {
19829
19902
  state: "stale",
@@ -19836,12 +19909,11 @@ function stalenessFromStatus(status) {
19836
19909
  reason: "The intent has not been reviewed as current project truth."
19837
19910
  };
19838
19911
  }
19839
- return { state: "fresh", horizonDays: 90 };
19840
- }
19841
- function anchorMatchesFile(anchor, file) {
19842
- const normalizedAnchor = anchor.replace(/^\.\//, "").trim();
19843
- const normalizedFile = file.replace(/^\.\//, "").trim();
19844
- return normalizedAnchor.length > 0 && normalizedFile.length > 0 && (normalizedFile.includes(normalizedAnchor) || normalizedAnchor.includes(normalizedFile));
19912
+ return {
19913
+ state: "fresh",
19914
+ horizonDays: freshnessHorizonDays,
19915
+ reason: freshnessHorizonDays === null ? "No freshness horizon was supplied by source metadata or policy." : null
19916
+ };
19845
19917
  }
19846
19918
  function intentEntryMatchesFile(entry, file) {
19847
19919
  return entry.affectedAnchors.some((anchor) => anchorMatchesFile(anchor, file));
@@ -19855,40 +19927,150 @@ function inferDecisionAnchors(decision, changedFiles) {
19855
19927
  );
19856
19928
  return uniqueStrings8(inferred);
19857
19929
  }
19858
- function splitIntentPhrases(value) {
19930
+ function normalizeIntentPhrase(value) {
19931
+ let normalized = value.trim();
19932
+ if (normalized.startsWith("- ") || normalized.startsWith("* ")) {
19933
+ normalized = normalized.slice(2).trim();
19934
+ }
19935
+ const dotIndex = normalized.indexOf(". ");
19936
+ if (dotIndex > 0) {
19937
+ const prefix = normalized.slice(0, dotIndex);
19938
+ if ([...prefix].every((char) => char >= "0" && char <= "9")) {
19939
+ normalized = normalized.slice(dotIndex + 2).trim();
19940
+ }
19941
+ }
19942
+ return normalized;
19943
+ }
19944
+ function splitExplicitIntentList(value) {
19859
19945
  if (!value) return [];
19860
- return uniqueStrings8(
19861
- value.split(/[;\n]/g).map((item) => item.replace(/^[-*]\s*/, "").trim()).filter(Boolean)
19862
- ).slice(0, 6);
19946
+ return uniqueStrings8(value.split(";").map(normalizeIntentPhrase).filter(Boolean)).slice(0, 6);
19947
+ }
19948
+ function normalizeIntentList(values) {
19949
+ return uniqueStrings8(values.flatMap((value) => splitExplicitIntentList(value))).slice(0, 8);
19950
+ }
19951
+ function emptyParsedIntentSections() {
19952
+ return {
19953
+ constraints: [],
19954
+ antiGoals: [],
19955
+ rejectedAlternatives: [],
19956
+ sourceFields: []
19957
+ };
19863
19958
  }
19864
- function extractAntiGoals(text) {
19865
- const matches = [...text.matchAll(/\b(?:avoid|forbid|prevent|do not|don't)\b[^.;\n]*/gi)].map(
19866
- (match) => match[0].trim()
19959
+ function intentSectionKey(label) {
19960
+ const normalized = normalizeIdentifier(label);
19961
+ for (const [key, aliases] of Object.entries(INTENT_SECTION_ALIASES)) {
19962
+ if (aliases.includes(normalized)) {
19963
+ return key;
19964
+ }
19965
+ }
19966
+ return void 0;
19967
+ }
19968
+ function separatorIndex(value) {
19969
+ const colon = value.indexOf(":");
19970
+ const equals = value.indexOf("=");
19971
+ if (colon < 0) return equals;
19972
+ if (equals < 0) return colon;
19973
+ return Math.min(colon, equals);
19974
+ }
19975
+ function addSectionValue(parsed, key, value) {
19976
+ const normalizedValue = normalizeIntentPhrase(value);
19977
+ if (!normalizedValue) return;
19978
+ parsed.sourceFields.push(key);
19979
+ if (key === "goal") {
19980
+ parsed.goal = parsed.goal ?? normalizedValue;
19981
+ return;
19982
+ }
19983
+ if (key === "owner") {
19984
+ parsed.owner = parsed.owner ?? normalizedValue;
19985
+ return;
19986
+ }
19987
+ if (key === "freshnessHorizonDays") {
19988
+ parsed.freshnessHorizonDays = parsed.freshnessHorizonDays ?? normalizeFreshnessHorizonDays(Number(normalizedValue));
19989
+ return;
19990
+ }
19991
+ parsed[key].push(...splitExplicitIntentList(normalizedValue));
19992
+ }
19993
+ function parseIntentSections(value) {
19994
+ const parsed = emptyParsedIntentSections();
19995
+ if (!value) return parsed;
19996
+ let currentKey;
19997
+ const lines = value.replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n");
19998
+ for (const rawLine of lines) {
19999
+ const line = normalizeIntentPhrase(rawLine);
20000
+ if (!line) continue;
20001
+ const splitAt = separatorIndex(line);
20002
+ if (splitAt > 0) {
20003
+ const maybeKey = intentSectionKey(line.slice(0, splitAt));
20004
+ if (maybeKey) {
20005
+ currentKey = maybeKey;
20006
+ addSectionValue(parsed, maybeKey, line.slice(splitAt + 1));
20007
+ continue;
20008
+ }
20009
+ }
20010
+ if (currentKey && (rawLine.trim().startsWith("- ") || rawLine.trim().startsWith("* "))) {
20011
+ addSectionValue(parsed, currentKey, line);
20012
+ }
20013
+ }
20014
+ return {
20015
+ ...parsed,
20016
+ constraints: uniqueStrings8(parsed.constraints).slice(0, 8),
20017
+ antiGoals: uniqueStrings8(parsed.antiGoals).slice(0, 8),
20018
+ rejectedAlternatives: uniqueStrings8(parsed.rejectedAlternatives).slice(0, 8),
20019
+ sourceFields: uniqueStrings8(parsed.sourceFields)
20020
+ };
20021
+ }
20022
+ function hasStructuredIntentFields(intent) {
20023
+ return Boolean(
20024
+ intent?.goal || intent?.owner || intent?.freshnessHorizonDays || (intent?.constraints?.length ?? 0) > 0 || (intent?.antiGoals?.length ?? 0) > 0 || (intent?.rejectedAlternatives?.length ?? 0) > 0
19867
20025
  );
19868
- return uniqueStrings8(matches).slice(0, 5);
19869
20026
  }
19870
- function extractRejectedAlternatives(text) {
19871
- const matches = [
19872
- ...text.matchAll(/\b(?:rejected|instead of|rather than|not use|not using)\b[^.;\n]*/gi)
19873
- ].map((match) => match[0].trim());
19874
- return uniqueStrings8(matches).slice(0, 5);
20027
+ function extractionMetadata(input) {
20028
+ const sectionFields = input.structuredSections.sourceFields.map((field) => `section:${field}`);
20029
+ return {
20030
+ mode: input.structuredFields ? "structured_fields" : sectionFields.length > 0 ? "structured_sections" : "legacy_source_fields",
20031
+ sourceFields: uniqueStrings8([
20032
+ input.structuredFields ? "intent" : void 0,
20033
+ ...sectionFields
20034
+ ]).slice(0, 12),
20035
+ fallbackUsed: input.fallbackUsed
20036
+ };
20037
+ }
20038
+ function textHasAnyToken(value, tokens) {
20039
+ const normalized = normalizeIdentifier(value);
20040
+ const parts = normalized.split("_").filter(Boolean);
20041
+ return tokens.some((token) => parts.includes(token));
19875
20042
  }
19876
20043
  function documentLooksLikeIntent(doc) {
19877
- const text = normalizeText([doc.path, doc.title, doc.contentPreview, doc.kind].join(" "));
19878
- return /\b(adr|decision|intent|rationale|constraint|architecture|runbook)\b/.test(text);
20044
+ if (hasStructuredIntentFields(doc.intent)) return true;
20045
+ if (parseIntentSections(doc.contentPreview).sourceFields.length > 0) return true;
20046
+ return [doc.path, doc.title, doc.kind].some(
20047
+ (value) => textHasAnyToken(value ?? "", INTENT_DOCUMENT_KIND_TOKENS)
20048
+ );
19879
20049
  }
19880
- function buildDecisionIntentEntry(decision, changedFiles) {
19881
- const status = intentStatusFromDecision(decision);
20050
+ function buildDecisionIntentEntry(decision, changedFiles, policy) {
20051
+ const status = statusFromDecisionStatus(decision);
19882
20052
  const anchors = inferDecisionAnchors(decision, changedFiles);
19883
- const fullText = decisionText(decision);
20053
+ const sectionIntent = parseIntentSections(
20054
+ [decision.scope, decision.rationale, decision.decision].join("\n")
20055
+ );
20056
+ const structuredFields = hasStructuredIntentFields(decision.intent);
20057
+ const fallbackGoal = decision.rationale || decision.decision || decision.title;
20058
+ const goal = decision.intent?.goal || sectionIntent.goal || fallbackGoal;
19884
20059
  const confidenceScore = normalizeConfidenceScore(
19885
20060
  decision.confidenceScore,
19886
20061
  status === "approved" ? 0.86 : 0.66
19887
20062
  );
20063
+ const freshnessHorizonDays = resolveFreshnessHorizonDays(
20064
+ decision.intent?.freshnessHorizonDays ?? decision.freshnessHorizonDays ?? sectionIntent.freshnessHorizonDays,
20065
+ policy
20066
+ );
19888
20067
  const reasonCodes = uniqueStrings8([
19889
20068
  "decision_intent",
19890
20069
  status === "approved" ? "reviewed_source" : "review_needed",
19891
- anchors.length > 0 ? "affected_anchors_present" : "affected_anchors_missing"
20070
+ anchors.length > 0 ? "affected_anchors_present" : "affected_anchors_missing",
20071
+ structuredFields ? "structured_intent_fields" : void 0,
20072
+ sectionIntent.sourceFields.length > 0 ? "structured_intent_sections" : void 0,
20073
+ goal === fallbackGoal ? "legacy_goal_fallback" : void 0
19892
20074
  ]);
19893
20075
  return {
19894
20076
  id: `intent:${decision.id}`,
@@ -19898,16 +20080,30 @@ function buildDecisionIntentEntry(decision, changedFiles) {
19898
20080
  sourceDecisionId: decision.id,
19899
20081
  sourceDocumentPath: null,
19900
20082
  intent: {
19901
- goal: decision.rationale || decision.decision || decision.title,
20083
+ goal,
19902
20084
  constraints: uniqueStrings8([
19903
20085
  ...decision.constraints ?? [],
19904
- ...splitIntentPhrases(decision.scope)
20086
+ ...decision.intent?.constraints ?? [],
20087
+ ...sectionIntent.constraints
19905
20088
  ]).slice(0, 8),
19906
- antiGoals: extractAntiGoals(fullText),
19907
- rejectedAlternatives: extractRejectedAlternatives(fullText)
20089
+ antiGoals: normalizeIntentList([
20090
+ ...decision.antiGoals ?? [],
20091
+ ...decision.intent?.antiGoals ?? [],
20092
+ ...sectionIntent.antiGoals
20093
+ ]),
20094
+ rejectedAlternatives: normalizeIntentList([
20095
+ ...decision.rejectedAlternatives ?? [],
20096
+ ...decision.intent?.rejectedAlternatives ?? [],
20097
+ ...sectionIntent.rejectedAlternatives
20098
+ ])
19908
20099
  },
20100
+ extraction: extractionMetadata({
20101
+ structuredFields,
20102
+ structuredSections: sectionIntent,
20103
+ fallbackUsed: goal === fallbackGoal
20104
+ }),
19909
20105
  affectedAnchors: anchors,
19910
- owner: null,
20106
+ owner: decision.intent?.owner ?? sectionIntent.owner ?? decision.owner ?? null,
19911
20107
  evidence: decision.evidence && decision.evidence.length > 0 ? decision.evidence : [
19912
20108
  {
19913
20109
  kind: "decision",
@@ -19917,7 +20113,7 @@ function buildDecisionIntentEntry(decision, changedFiles) {
19917
20113
  }
19918
20114
  ],
19919
20115
  confidence: intentConfidence(confidenceScore, status, reasonCodes),
19920
- staleness: stalenessFromStatus(status),
20116
+ staleness: stalenessFromStatus(status, freshnessHorizonDays),
19921
20117
  reasonCodes,
19922
20118
  recommendedActions: status === "approved" ? ["Keep this intent linked when changing its affected anchors."] : ["Review this intent candidate before treating it as governing project truth."],
19923
20119
  caveats: [
@@ -19925,10 +20121,22 @@ function buildDecisionIntentEntry(decision, changedFiles) {
19925
20121
  ]
19926
20122
  };
19927
20123
  }
19928
- function buildDocumentIntentEntry(doc) {
19929
- const preview4 = doc.contentPreview || doc.title || doc.path;
19930
- const reasonCodes = ["document_intent_candidate", "review_needed"];
19931
- const confidenceScore = doc.kind && /adr|decision/i.test(doc.kind) ? 0.66 : 0.58;
20124
+ function buildDocumentIntentEntry(doc, policy) {
20125
+ const sectionIntent = parseIntentSections(doc.contentPreview);
20126
+ const structuredFields = hasStructuredIntentFields(doc.intent);
20127
+ const goal = doc.intent?.goal ?? sectionIntent.goal ?? doc.title ?? doc.path;
20128
+ const reasonCodes = uniqueStrings8([
20129
+ "document_intent_candidate",
20130
+ "review_needed",
20131
+ structuredFields ? "structured_intent_fields" : void 0,
20132
+ sectionIntent.sourceFields.length > 0 ? "structured_intent_sections" : void 0,
20133
+ goal === doc.path || goal === doc.title ? "legacy_goal_fallback" : void 0
20134
+ ]);
20135
+ const confidenceScore = textHasAnyToken(doc.kind ?? "", ["adr", "decision"]) ? 0.66 : 0.58;
20136
+ const freshnessHorizonDays = resolveFreshnessHorizonDays(
20137
+ doc.intent?.freshnessHorizonDays ?? doc.freshnessHorizonDays ?? sectionIntent.freshnessHorizonDays,
20138
+ policy
20139
+ );
19932
20140
  return {
19933
20141
  id: `intent:doc:${doc.path}`,
19934
20142
  title: doc.title || doc.path,
@@ -19937,13 +20145,27 @@ function buildDocumentIntentEntry(doc) {
19937
20145
  sourceDecisionId: null,
19938
20146
  sourceDocumentPath: doc.path,
19939
20147
  intent: {
19940
- goal: preview4,
19941
- constraints: splitIntentPhrases(doc.contentPreview).slice(0, 4),
19942
- antiGoals: extractAntiGoals(preview4),
19943
- rejectedAlternatives: extractRejectedAlternatives(preview4)
20148
+ goal,
20149
+ constraints: uniqueStrings8([
20150
+ ...doc.intent?.constraints ?? [],
20151
+ ...sectionIntent.constraints
20152
+ ]).slice(0, 4),
20153
+ antiGoals: normalizeIntentList([
20154
+ ...doc.intent?.antiGoals ?? [],
20155
+ ...sectionIntent.antiGoals
20156
+ ]),
20157
+ rejectedAlternatives: normalizeIntentList([
20158
+ ...doc.intent?.rejectedAlternatives ?? [],
20159
+ ...sectionIntent.rejectedAlternatives
20160
+ ])
19944
20161
  },
20162
+ extraction: extractionMetadata({
20163
+ structuredFields,
20164
+ structuredSections: sectionIntent,
20165
+ fallbackUsed: goal === doc.path || goal === doc.title
20166
+ }),
19945
20167
  affectedAnchors: [doc.path],
19946
- owner: null,
20168
+ owner: doc.intent?.owner ?? sectionIntent.owner ?? doc.owner ?? null,
19947
20169
  evidence: [
19948
20170
  {
19949
20171
  kind: "document",
@@ -19955,7 +20177,8 @@ function buildDocumentIntentEntry(doc) {
19955
20177
  confidence: intentConfidence(confidenceScore, "review_pending", reasonCodes),
19956
20178
  staleness: {
19957
20179
  state: "unknown",
19958
- reason: "Document-derived intent candidates need review before becoming governing truth."
20180
+ reason: "Document-derived intent candidates need review before becoming governing truth.",
20181
+ horizonDays: freshnessHorizonDays
19959
20182
  },
19960
20183
  reasonCodes,
19961
20184
  recommendedActions: ["Promote, reject, or link this document intent candidate to a decision."],
@@ -19966,15 +20189,19 @@ function buildDocumentIntentEntry(doc) {
19966
20189
  }
19967
20190
  function buildProjectIntentLedger(input, changedFilesOverride) {
19968
20191
  const changedFiles = uniqueStrings8(changedFilesOverride ?? input.changedFiles);
20192
+ const policy = {
20193
+ ...DEFAULT_PROJECT_INTENT_LEDGER_POLICY,
20194
+ ...input.intentPolicy ?? {}
20195
+ };
19969
20196
  const decisionEntries = (input.decisions ?? []).map(
19970
- (decision) => buildDecisionIntentEntry(decision, changedFiles)
20197
+ (decision) => buildDecisionIntentEntry(decision, changedFiles, policy)
19971
20198
  );
19972
20199
  const decisionDocumentPaths = new Set(
19973
20200
  decisionEntries.flatMap(
19974
20201
  (entry) => entry.evidence.map((evidence) => evidence.sourceRef).filter(Boolean)
19975
20202
  )
19976
20203
  );
19977
- const documentEntries = (input.documents ?? []).filter(documentLooksLikeIntent).filter((doc) => !decisionDocumentPaths.has(doc.path)).map(buildDocumentIntentEntry);
20204
+ const documentEntries = (input.documents ?? []).filter(documentLooksLikeIntent).filter((doc) => !decisionDocumentPaths.has(doc.path)).map((doc) => buildDocumentIntentEntry(doc, policy));
19978
20205
  const entries = [...decisionEntries, ...documentEntries];
19979
20206
  const linkedIntentIds = new Set(
19980
20207
  entries.filter((entry) => changedFiles.some((file) => intentEntryMatchesFile(entry, file))).map((entry) => entry.id)
@@ -20006,11 +20233,11 @@ function buildProjectIntentLedger(input, changedFilesOverride) {
20006
20233
  ].filter((item) => Boolean(item))
20007
20234
  };
20008
20235
  }
20236
+
20237
+ // ../project-intelligence-contracts/src/unknown-registry.ts
20238
+ var PROJECT_UNKNOWN_REGISTRY_VERSION = "unknown-registry-v1";
20009
20239
  function unknownId(category, key, scope) {
20010
- return makeFindingId("decision_vs_code", `${category}:${key}`, scope).replace(
20011
- "reality:decision-vs-code",
20012
- "unknown"
20013
- );
20240
+ return makeScopedId("unknown", category, key, scope);
20014
20241
  }
20015
20242
  function unknownStatusFromSeverity(unknowns) {
20016
20243
  if (unknowns.some(
@@ -20236,8 +20463,23 @@ function buildProjectUnknownRegistry(input) {
20236
20463
  ].filter((item) => Boolean(item))
20237
20464
  };
20238
20465
  }
20239
- function surfaceFiles(surface, changedFiles) {
20240
- return changedFiles.filter((file) => surface.patterns.some((pattern) => pattern.test(file)));
20466
+
20467
+ // ../project-intelligence-contracts/src/reality-check.ts
20468
+ var PROJECT_REALITY_CHECK_VERSION = "project-reality-check-v0";
20469
+ function findingSeverityScore(severity) {
20470
+ if (severity === "blocking") return 0;
20471
+ if (severity === "review_required") return 30;
20472
+ if (severity === "watch") return 70;
20473
+ return 90;
20474
+ }
20475
+ function resultStatusFromFindings(findings) {
20476
+ if (findings.some((finding) => finding.severity === "blocking")) return "blocking";
20477
+ if (findings.some((finding) => finding.severity === "review_required")) return "review_required";
20478
+ if (findings.some((finding) => finding.severity === "watch")) return "advisory";
20479
+ return "pass";
20480
+ }
20481
+ function makeFindingId(type, key, files) {
20482
+ return makeScopedId("reality", type, key, files);
20241
20483
  }
20242
20484
  function testFiles(changedFiles) {
20243
20485
  return changedFiles.filter(
@@ -26069,17 +26311,17 @@ function normalizeCollaborationFiles(files) {
26069
26311
  function parseCollaborationResources(values) {
26070
26312
  return normalizeResources(
26071
26313
  (values ?? []).map((value) => {
26072
- const separatorIndex = value.indexOf(":");
26073
- if (separatorIndex <= 0) {
26314
+ const separatorIndex2 = value.indexOf(":");
26315
+ if (separatorIndex2 <= 0) {
26074
26316
  throw new Error(`Resource must use KIND:id format: ${value}`);
26075
26317
  }
26076
- const kind = normalizeResourceKind(value.slice(0, separatorIndex));
26318
+ const kind = normalizeResourceKind(value.slice(0, separatorIndex2));
26077
26319
  if (!kind) {
26078
26320
  throw new Error(
26079
- `Unsupported collaboration resource kind: ${value.slice(0, separatorIndex)}`
26321
+ `Unsupported collaboration resource kind: ${value.slice(0, separatorIndex2)}`
26080
26322
  );
26081
26323
  }
26082
- const id = value.slice(separatorIndex + 1).trim();
26324
+ const id = value.slice(separatorIndex2 + 1).trim();
26083
26325
  if (!id) {
26084
26326
  throw new Error(`Resource id is required for ${kind}`);
26085
26327
  }
@@ -27609,12 +27851,12 @@ function parseKeyValueEntries(entries, label) {
27609
27851
  }
27610
27852
  const result = {};
27611
27853
  for (const entry of entries) {
27612
- const separatorIndex = entry.indexOf("=");
27613
- if (separatorIndex <= 0 || separatorIndex === entry.length - 1) {
27854
+ const separatorIndex2 = entry.indexOf("=");
27855
+ if (separatorIndex2 <= 0 || separatorIndex2 === entry.length - 1) {
27614
27856
  throw new Error(`Invalid ${label} entry '${entry}'. Expected KEY=value.`);
27615
27857
  }
27616
- const key = entry.slice(0, separatorIndex).trim();
27617
- const value = entry.slice(separatorIndex + 1).trim();
27858
+ const key = entry.slice(0, separatorIndex2).trim();
27859
+ const value = entry.slice(separatorIndex2 + 1).trim();
27618
27860
  if (!key || !value) {
27619
27861
  throw new Error(`Invalid ${label} entry '${entry}'. Expected KEY=value.`);
27620
27862
  }
@@ -201,7 +201,12 @@ snipara-companion workflow resume --include-session-context
201
201
  dirty local evidence, architecture drift, stale/review-pending intent, and
202
202
  heuristic-calibration gaps. Keep `--enforce` opt-in for narrow hooks or
203
203
  calibrated CI adapters; the V1 heuristics are advisory and can produce false
204
- positives on broad text/path matches.
204
+ positives on broad text/path matches. Intent Ledger extraction prefers
205
+ structured contract fields or explicit labeled sections such as `Goal:`,
206
+ `Constraints:`, `Anti-goals:`, `Rejected alternatives:`, `Owner:`, and
207
+ `Freshness horizon days:`. Generic prose remains a legacy fallback for goal
208
+ text only; anti-goals and rejected alternatives are not inferred from loose
209
+ words in free text.
205
210
  - `timeline` is the Git-style log for workflow starts, phase starts, phase
206
211
  commits, final commits, and Team Sync handoffs.
207
212
  - `workflow impact-gate` audits committed local workflow phases that are ahead
@@ -948,7 +953,7 @@ CI adapter needs the contradiction-to-reality gate without a full hosted brief:
948
953
  snipara-companion reality-check \
949
954
  --task "refactor auth middleware" \
950
955
  --changed-files src/auth/middleware.ts \
951
- --decision "DEC-001: auth middleware must keep side effects explicit" \
956
+ --decision $'DEC-001: Goal: keep auth middleware side effects explicit\nConstraints:\n- auth middleware stays synchronous\nAnti-goals:\n- implicit token refresh' \
952
957
  --verification "pnpm test auth" \
953
958
  --enforce
954
959
  ```
@@ -958,7 +963,9 @@ The command reads the local Git scope by default, includes dirty files unless
958
963
  hooks or CI collectors. `--enforce` exits non-zero for `review_required` or
959
964
  `blocking` findings, but should stay opt-in until the matched surfaces,
960
965
  verification signals, and project-specific thresholds have been calibrated.
961
- JSON output is available with `--json`.
966
+ Intent can be supplied as structured sections inside `--decision` or
967
+ `--document`, using labels such as `Goal:`, `Constraints:`, `Anti-goals:`,
968
+ `Rejected alternatives:`, and `Owner:`. JSON output is available with `--json`.
962
969
 
963
970
  Use `intelligence ledger-export` when an agent run, review, or replay benchmark
964
971
  needs a portable Coding Intelligence Ledger instead of a raw transcript:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snipara-companion",
3
- "version": "3.0.8",
3
+ "version": "3.0.10",
4
4
  "description": "Local-first CLI that asks your repo what breaks before an AI coding agent edits it.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {