snipara-companion 3.0.9 → 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 +290 -72
- package/docs/FULL_REFERENCE.md +10 -3
- package/package.json +1 -1
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
|
|
9754
|
-
if (
|
|
9753
|
+
const separatorIndex2 = withoutExport.indexOf("=");
|
|
9754
|
+
if (separatorIndex2 <= 0) {
|
|
9755
9755
|
return null;
|
|
9756
9756
|
}
|
|
9757
|
-
const key = withoutExport.slice(0,
|
|
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(
|
|
9761
|
+
const value = parseEnvValue(withoutExport.slice(separatorIndex2 + 1).trim());
|
|
9762
9762
|
return { key, value };
|
|
9763
9763
|
}
|
|
9764
9764
|
function parseEnvValue(rawValue) {
|
|
@@ -19782,7 +19782,15 @@ function decisionText(decision) {
|
|
|
19782
19782
|
decision.decision,
|
|
19783
19783
|
decision.rationale,
|
|
19784
19784
|
decision.scope,
|
|
19785
|
-
|
|
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 ?? []
|
|
19786
19794
|
].filter(Boolean).join(" ");
|
|
19787
19795
|
}
|
|
19788
19796
|
function confidenceBand(score) {
|
|
@@ -19805,26 +19813,73 @@ function surfaceFiles(surface, changedFiles) {
|
|
|
19805
19813
|
|
|
19806
19814
|
// ../project-intelligence-contracts/src/intent-ledger.ts
|
|
19807
19815
|
var PROJECT_INTENT_LEDGER_VERSION = "intent-ledger-v1";
|
|
19808
|
-
var
|
|
19809
|
-
var
|
|
19810
|
-
|
|
19811
|
-
{ status: "superseded", patterns: [/supersed/i, /replac/i] },
|
|
19812
|
-
{ status: "stale", patterns: [/stale/i, /expired/i, /deprecated/i] },
|
|
19813
|
-
{ status: "approved", patterns: [/approved/i, /accepted/i, /active/i, /confirmed/i] }
|
|
19814
|
-
];
|
|
19815
|
-
var INTENT_PHRASE_SPLIT_PATTERN = /[;\n]/g;
|
|
19816
|
-
var INTENT_MARKDOWN_LIST_MARKER_PATTERN = /^[-*]\s*/;
|
|
19817
|
-
var INTENT_NUMBERED_LIST_MARKER_PATTERN = /^\d+\.\s*/;
|
|
19818
|
-
var INTENT_TEXT_EXTRACTORS = {
|
|
19819
|
-
antiGoals: [/\b(?:avoid|forbid|prevent|do not|don't)\b[^.;\n]*/gi],
|
|
19820
|
-
rejectedAlternatives: [/\b(?:rejected|instead of|rather than|not use|not using)\b[^.;\n]*/gi]
|
|
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
|
|
19821
19819
|
};
|
|
19822
|
-
|
|
19823
|
-
|
|
19824
|
-
|
|
19825
|
-
|
|
19826
|
-
|
|
19827
|
-
|
|
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
|
+
}
|
|
19882
|
+
return "review_pending";
|
|
19828
19883
|
}
|
|
19829
19884
|
function intentConfidence(score, status, reasonCodes) {
|
|
19830
19885
|
return {
|
|
@@ -19834,7 +19889,14 @@ function intentConfidence(score, status, reasonCodes) {
|
|
|
19834
19889
|
reasonCodes
|
|
19835
19890
|
};
|
|
19836
19891
|
}
|
|
19837
|
-
function
|
|
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) {
|
|
19838
19900
|
if (status === "stale" || status === "superseded") {
|
|
19839
19901
|
return {
|
|
19840
19902
|
state: "stale",
|
|
@@ -19847,7 +19909,11 @@ function stalenessFromStatus(status) {
|
|
|
19847
19909
|
reason: "The intent has not been reviewed as current project truth."
|
|
19848
19910
|
};
|
|
19849
19911
|
}
|
|
19850
|
-
return {
|
|
19912
|
+
return {
|
|
19913
|
+
state: "fresh",
|
|
19914
|
+
horizonDays: freshnessHorizonDays,
|
|
19915
|
+
reason: freshnessHorizonDays === null ? "No freshness horizon was supplied by source metadata or policy." : null
|
|
19916
|
+
};
|
|
19851
19917
|
}
|
|
19852
19918
|
function intentEntryMatchesFile(entry, file) {
|
|
19853
19919
|
return entry.affectedAnchors.some((anchor) => anchorMatchesFile(anchor, file));
|
|
@@ -19862,42 +19928,149 @@ function inferDecisionAnchors(decision, changedFiles) {
|
|
|
19862
19928
|
return uniqueStrings8(inferred);
|
|
19863
19929
|
}
|
|
19864
19930
|
function normalizeIntentPhrase(value) {
|
|
19865
|
-
|
|
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;
|
|
19866
19943
|
}
|
|
19867
|
-
function
|
|
19944
|
+
function splitExplicitIntentList(value) {
|
|
19868
19945
|
if (!value) return [];
|
|
19869
|
-
return uniqueStrings8(
|
|
19870
|
-
value.split(INTENT_PHRASE_SPLIT_PATTERN).map(normalizeIntentPhrase).filter(Boolean)
|
|
19871
|
-
).slice(0, 6);
|
|
19946
|
+
return uniqueStrings8(value.split(";").map(normalizeIntentPhrase).filter(Boolean)).slice(0, 6);
|
|
19872
19947
|
}
|
|
19873
|
-
function
|
|
19874
|
-
|
|
19875
|
-
|
|
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
|
+
};
|
|
19958
|
+
}
|
|
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
|
|
19876
20025
|
);
|
|
19877
|
-
return uniqueStrings8(matches).slice(0, limit);
|
|
19878
20026
|
}
|
|
19879
|
-
function
|
|
19880
|
-
|
|
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
|
+
};
|
|
19881
20037
|
}
|
|
19882
|
-
function
|
|
19883
|
-
|
|
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));
|
|
19884
20042
|
}
|
|
19885
20043
|
function documentLooksLikeIntent(doc) {
|
|
19886
|
-
|
|
19887
|
-
|
|
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
|
+
);
|
|
19888
20049
|
}
|
|
19889
|
-
function buildDecisionIntentEntry(decision, changedFiles) {
|
|
19890
|
-
const status =
|
|
20050
|
+
function buildDecisionIntentEntry(decision, changedFiles, policy) {
|
|
20051
|
+
const status = statusFromDecisionStatus(decision);
|
|
19891
20052
|
const anchors = inferDecisionAnchors(decision, changedFiles);
|
|
19892
|
-
const
|
|
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;
|
|
19893
20059
|
const confidenceScore = normalizeConfidenceScore(
|
|
19894
20060
|
decision.confidenceScore,
|
|
19895
20061
|
status === "approved" ? 0.86 : 0.66
|
|
19896
20062
|
);
|
|
20063
|
+
const freshnessHorizonDays = resolveFreshnessHorizonDays(
|
|
20064
|
+
decision.intent?.freshnessHorizonDays ?? decision.freshnessHorizonDays ?? sectionIntent.freshnessHorizonDays,
|
|
20065
|
+
policy
|
|
20066
|
+
);
|
|
19897
20067
|
const reasonCodes = uniqueStrings8([
|
|
19898
20068
|
"decision_intent",
|
|
19899
20069
|
status === "approved" ? "reviewed_source" : "review_needed",
|
|
19900
|
-
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
|
|
19901
20074
|
]);
|
|
19902
20075
|
return {
|
|
19903
20076
|
id: `intent:${decision.id}`,
|
|
@@ -19907,16 +20080,30 @@ function buildDecisionIntentEntry(decision, changedFiles) {
|
|
|
19907
20080
|
sourceDecisionId: decision.id,
|
|
19908
20081
|
sourceDocumentPath: null,
|
|
19909
20082
|
intent: {
|
|
19910
|
-
goal
|
|
20083
|
+
goal,
|
|
19911
20084
|
constraints: uniqueStrings8([
|
|
19912
20085
|
...decision.constraints ?? [],
|
|
19913
|
-
...
|
|
20086
|
+
...decision.intent?.constraints ?? [],
|
|
20087
|
+
...sectionIntent.constraints
|
|
19914
20088
|
]).slice(0, 8),
|
|
19915
|
-
antiGoals:
|
|
19916
|
-
|
|
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
|
+
])
|
|
19917
20099
|
},
|
|
20100
|
+
extraction: extractionMetadata({
|
|
20101
|
+
structuredFields,
|
|
20102
|
+
structuredSections: sectionIntent,
|
|
20103
|
+
fallbackUsed: goal === fallbackGoal
|
|
20104
|
+
}),
|
|
19918
20105
|
affectedAnchors: anchors,
|
|
19919
|
-
owner: null,
|
|
20106
|
+
owner: decision.intent?.owner ?? sectionIntent.owner ?? decision.owner ?? null,
|
|
19920
20107
|
evidence: decision.evidence && decision.evidence.length > 0 ? decision.evidence : [
|
|
19921
20108
|
{
|
|
19922
20109
|
kind: "decision",
|
|
@@ -19926,7 +20113,7 @@ function buildDecisionIntentEntry(decision, changedFiles) {
|
|
|
19926
20113
|
}
|
|
19927
20114
|
],
|
|
19928
20115
|
confidence: intentConfidence(confidenceScore, status, reasonCodes),
|
|
19929
|
-
staleness: stalenessFromStatus(status),
|
|
20116
|
+
staleness: stalenessFromStatus(status, freshnessHorizonDays),
|
|
19930
20117
|
reasonCodes,
|
|
19931
20118
|
recommendedActions: status === "approved" ? ["Keep this intent linked when changing its affected anchors."] : ["Review this intent candidate before treating it as governing project truth."],
|
|
19932
20119
|
caveats: [
|
|
@@ -19934,10 +20121,22 @@ function buildDecisionIntentEntry(decision, changedFiles) {
|
|
|
19934
20121
|
]
|
|
19935
20122
|
};
|
|
19936
20123
|
}
|
|
19937
|
-
function buildDocumentIntentEntry(doc) {
|
|
19938
|
-
const
|
|
19939
|
-
const
|
|
19940
|
-
const
|
|
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
|
+
);
|
|
19941
20140
|
return {
|
|
19942
20141
|
id: `intent:doc:${doc.path}`,
|
|
19943
20142
|
title: doc.title || doc.path,
|
|
@@ -19946,13 +20145,27 @@ function buildDocumentIntentEntry(doc) {
|
|
|
19946
20145
|
sourceDecisionId: null,
|
|
19947
20146
|
sourceDocumentPath: doc.path,
|
|
19948
20147
|
intent: {
|
|
19949
|
-
goal
|
|
19950
|
-
constraints:
|
|
19951
|
-
|
|
19952
|
-
|
|
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
|
+
])
|
|
19953
20161
|
},
|
|
20162
|
+
extraction: extractionMetadata({
|
|
20163
|
+
structuredFields,
|
|
20164
|
+
structuredSections: sectionIntent,
|
|
20165
|
+
fallbackUsed: goal === doc.path || goal === doc.title
|
|
20166
|
+
}),
|
|
19954
20167
|
affectedAnchors: [doc.path],
|
|
19955
|
-
owner: null,
|
|
20168
|
+
owner: doc.intent?.owner ?? sectionIntent.owner ?? doc.owner ?? null,
|
|
19956
20169
|
evidence: [
|
|
19957
20170
|
{
|
|
19958
20171
|
kind: "document",
|
|
@@ -19964,7 +20177,8 @@ function buildDocumentIntentEntry(doc) {
|
|
|
19964
20177
|
confidence: intentConfidence(confidenceScore, "review_pending", reasonCodes),
|
|
19965
20178
|
staleness: {
|
|
19966
20179
|
state: "unknown",
|
|
19967
|
-
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
|
|
19968
20182
|
},
|
|
19969
20183
|
reasonCodes,
|
|
19970
20184
|
recommendedActions: ["Promote, reject, or link this document intent candidate to a decision."],
|
|
@@ -19975,15 +20189,19 @@ function buildDocumentIntentEntry(doc) {
|
|
|
19975
20189
|
}
|
|
19976
20190
|
function buildProjectIntentLedger(input, changedFilesOverride) {
|
|
19977
20191
|
const changedFiles = uniqueStrings8(changedFilesOverride ?? input.changedFiles);
|
|
20192
|
+
const policy = {
|
|
20193
|
+
...DEFAULT_PROJECT_INTENT_LEDGER_POLICY,
|
|
20194
|
+
...input.intentPolicy ?? {}
|
|
20195
|
+
};
|
|
19978
20196
|
const decisionEntries = (input.decisions ?? []).map(
|
|
19979
|
-
(decision) => buildDecisionIntentEntry(decision, changedFiles)
|
|
20197
|
+
(decision) => buildDecisionIntentEntry(decision, changedFiles, policy)
|
|
19980
20198
|
);
|
|
19981
20199
|
const decisionDocumentPaths = new Set(
|
|
19982
20200
|
decisionEntries.flatMap(
|
|
19983
20201
|
(entry) => entry.evidence.map((evidence) => evidence.sourceRef).filter(Boolean)
|
|
19984
20202
|
)
|
|
19985
20203
|
);
|
|
19986
|
-
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));
|
|
19987
20205
|
const entries = [...decisionEntries, ...documentEntries];
|
|
19988
20206
|
const linkedIntentIds = new Set(
|
|
19989
20207
|
entries.filter((entry) => changedFiles.some((file) => intentEntryMatchesFile(entry, file))).map((entry) => entry.id)
|
|
@@ -26093,17 +26311,17 @@ function normalizeCollaborationFiles(files) {
|
|
|
26093
26311
|
function parseCollaborationResources(values) {
|
|
26094
26312
|
return normalizeResources(
|
|
26095
26313
|
(values ?? []).map((value) => {
|
|
26096
|
-
const
|
|
26097
|
-
if (
|
|
26314
|
+
const separatorIndex2 = value.indexOf(":");
|
|
26315
|
+
if (separatorIndex2 <= 0) {
|
|
26098
26316
|
throw new Error(`Resource must use KIND:id format: ${value}`);
|
|
26099
26317
|
}
|
|
26100
|
-
const kind = normalizeResourceKind(value.slice(0,
|
|
26318
|
+
const kind = normalizeResourceKind(value.slice(0, separatorIndex2));
|
|
26101
26319
|
if (!kind) {
|
|
26102
26320
|
throw new Error(
|
|
26103
|
-
`Unsupported collaboration resource kind: ${value.slice(0,
|
|
26321
|
+
`Unsupported collaboration resource kind: ${value.slice(0, separatorIndex2)}`
|
|
26104
26322
|
);
|
|
26105
26323
|
}
|
|
26106
|
-
const id = value.slice(
|
|
26324
|
+
const id = value.slice(separatorIndex2 + 1).trim();
|
|
26107
26325
|
if (!id) {
|
|
26108
26326
|
throw new Error(`Resource id is required for ${kind}`);
|
|
26109
26327
|
}
|
|
@@ -27633,12 +27851,12 @@ function parseKeyValueEntries(entries, label) {
|
|
|
27633
27851
|
}
|
|
27634
27852
|
const result = {};
|
|
27635
27853
|
for (const entry of entries) {
|
|
27636
|
-
const
|
|
27637
|
-
if (
|
|
27854
|
+
const separatorIndex2 = entry.indexOf("=");
|
|
27855
|
+
if (separatorIndex2 <= 0 || separatorIndex2 === entry.length - 1) {
|
|
27638
27856
|
throw new Error(`Invalid ${label} entry '${entry}'. Expected KEY=value.`);
|
|
27639
27857
|
}
|
|
27640
|
-
const key = entry.slice(0,
|
|
27641
|
-
const value = entry.slice(
|
|
27858
|
+
const key = entry.slice(0, separatorIndex2).trim();
|
|
27859
|
+
const value = entry.slice(separatorIndex2 + 1).trim();
|
|
27642
27860
|
if (!key || !value) {
|
|
27643
27861
|
throw new Error(`Invalid ${label} entry '${entry}'. Expected KEY=value.`);
|
|
27644
27862
|
}
|
package/docs/FULL_REFERENCE.md
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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:
|