@isaacriehm/cairn-core 0.3.7 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/attention/bulk-accept.d.ts +18 -0
- package/dist/attention/bulk-accept.js +46 -0
- package/dist/attention/bulk-accept.js.map +1 -1
- package/dist/attention/dedup.d.ts +68 -0
- package/dist/attention/dedup.js +239 -0
- package/dist/attention/dedup.js.map +1 -0
- package/dist/attention/index.d.ts +3 -0
- package/dist/attention/index.js +3 -0
- package/dist/attention/index.js.map +1 -1
- package/dist/attention/restore.d.ts +43 -0
- package/dist/attention/restore.js +127 -0
- package/dist/attention/restore.js.map +1 -0
- package/dist/attention/source-strip.d.ts +49 -0
- package/dist/attention/source-strip.js +340 -0
- package/dist/attention/source-strip.js.map +1 -0
- package/dist/claude/cache.d.ts +31 -0
- package/dist/claude/cache.js +0 -0
- package/dist/claude/cache.js.map +1 -0
- package/dist/claude/error.d.ts +1 -1
- package/dist/claude/error.js +5 -0
- package/dist/claude/error.js.map +1 -1
- package/dist/claude/runner.js +60 -3
- package/dist/claude/runner.js.map +1 -1
- package/dist/claude/types.d.ts +25 -0
- package/dist/hooks/runners/session-start.js +24 -2
- package/dist/hooks/runners/session-start.js.map +1 -1
- package/dist/init/baseline-audit.d.ts +8 -0
- package/dist/init/baseline-audit.js +1 -1
- package/dist/init/baseline-audit.js.map +1 -1
- package/dist/init/brand-derive.js +53 -34
- package/dist/init/brand-derive.js.map +1 -1
- package/dist/init/index.d.ts +4 -2
- package/dist/init/index.js +3 -1
- package/dist/init/index.js.map +1 -1
- package/dist/init/ingest-docs.d.ts +7 -0
- package/dist/init/ingest-docs.js +4 -1
- package/dist/init/ingest-docs.js.map +1 -1
- package/dist/init/mapper-merge.js +1 -0
- package/dist/init/mapper-merge.js.map +1 -1
- package/dist/init/phases/3-mapper.js +18 -0
- package/dist/init/phases/3-mapper.js.map +1 -1
- package/dist/init/phases/5-brand.js +3 -0
- package/dist/init/phases/5-brand.js.map +1 -1
- package/dist/init/phases/6-docs-ingest.js +14 -0
- package/dist/init/phases/6-docs-ingest.js.map +1 -1
- package/dist/init/phases/7b-source-comments.d.ts +9 -0
- package/dist/init/phases/7b-source-comments.js +41 -1
- package/dist/init/phases/7b-source-comments.js.map +1 -1
- package/dist/init/phases/7c-rules-merge.js +10 -0
- package/dist/init/phases/7c-rules-merge.js.map +1 -1
- package/dist/init/phases/index.d.ts +2 -0
- package/dist/init/phases/index.js +2 -0
- package/dist/init/phases/index.js.map +1 -1
- package/dist/init/phases/parallel-678.d.ts +31 -0
- package/dist/init/phases/parallel-678.js +205 -0
- package/dist/init/phases/parallel-678.js.map +1 -0
- package/dist/init/phases/source-comments-output-io.d.ts +78 -0
- package/dist/init/phases/source-comments-output-io.js +84 -0
- package/dist/init/phases/source-comments-output-io.js.map +1 -0
- package/dist/init/progress.d.ts +35 -0
- package/dist/init/progress.js +71 -0
- package/dist/init/progress.js.map +1 -0
- package/dist/init/rules-merge/ingest.d.ts +14 -0
- package/dist/init/rules-merge/ingest.js +8 -2
- package/dist/init/rules-merge/ingest.js.map +1 -1
- package/dist/init/source-comments/classify.d.ts +6 -0
- package/dist/init/source-comments/classify.js +60 -3
- package/dist/init/source-comments/classify.js.map +1 -1
- package/dist/init/source-comments/ingest.d.ts +21 -0
- package/dist/init/source-comments/ingest.js +28 -3
- package/dist/init/source-comments/ingest.js.map +1 -1
- package/dist/mcp/tools/attention-dedup.d.ts +21 -0
- package/dist/mcp/tools/attention-dedup.js +34 -0
- package/dist/mcp/tools/attention-dedup.js.map +1 -0
- package/dist/mcp/tools/attention-restore.d.ts +14 -0
- package/dist/mcp/tools/attention-restore.js +22 -0
- package/dist/mcp/tools/attention-restore.js.map +1 -0
- package/dist/mcp/tools/index.js +6 -1
- package/dist/mcp/tools/index.js.map +1 -1
- package/dist/mcp/tools/init-phases.d.ts +1 -0
- package/dist/mcp/tools/init-phases.js +88 -1
- package/dist/mcp/tools/init-phases.js.map +1 -1
- package/dist/mcp/tools/resolve-attention.js +44 -141
- package/dist/mcp/tools/resolve-attention.js.map +1 -1
- package/dist/status-line/format.d.ts +5 -2
- package/dist/status-line/format.js +31 -5
- package/dist/status-line/format.js.map +1 -1
- package/dist/status-line/reader.js +25 -1
- package/dist/status-line/reader.js.map +1 -1
- package/package.json +1 -1
- package/templates/.claude/rules/cairn.md +47 -0
|
@@ -52,6 +52,24 @@ export interface BulkAcceptResult {
|
|
|
52
52
|
acceptedIds: string[];
|
|
53
53
|
invariantsScanned: number;
|
|
54
54
|
invariantsByConfidence: Record<DraftConfidence, number>;
|
|
55
|
+
/**
|
|
56
|
+
* Aggregate count of files where an accepted DEC's source-comment
|
|
57
|
+
* essay was replaced inline with `// §DEC-NNNN`. Mirrors the §INV
|
|
58
|
+
* strip pass that 7b runs at adoption time.
|
|
59
|
+
*/
|
|
60
|
+
sourceStripFilesModified: number;
|
|
61
|
+
/** Aggregate count of strip items that landed across all accepted DECs. */
|
|
62
|
+
sourceStripItemsApplied: number;
|
|
63
|
+
/**
|
|
64
|
+
* Per-accepted-id strip outcome reasons when the strip didn't run
|
|
65
|
+
* the happy path (block not found, audit missing, source dirty).
|
|
66
|
+
* Empty when every accept succeeded or no DECs came from source
|
|
67
|
+
* comments.
|
|
68
|
+
*/
|
|
69
|
+
sourceStripSkipped: {
|
|
70
|
+
id: string;
|
|
71
|
+
reason: string;
|
|
72
|
+
}[];
|
|
55
73
|
dryRun: boolean;
|
|
56
74
|
}
|
|
57
75
|
/**
|
|
@@ -34,6 +34,7 @@ import { decisionsDir, invariantsDir } from "../ground/paths.js";
|
|
|
34
34
|
import { writeDecisionsLedger } from "../ground/ledgers.js";
|
|
35
35
|
import { withWriteLock } from "../lock.js";
|
|
36
36
|
import { scoreDecDraft, scoreInvariant, } from "./scoring.js";
|
|
37
|
+
import { runDecSourceStrip } from "./source-strip.js";
|
|
37
38
|
/**
|
|
38
39
|
* Score, stamp, and (for high-confidence DECs) accept inbox drafts in
|
|
39
40
|
* bulk. Returns the count summary the CLI / skill renders to the
|
|
@@ -49,6 +50,9 @@ export async function bulkAcceptObvious(args) {
|
|
|
49
50
|
decsAccepted: 0,
|
|
50
51
|
decsByConfidence: { high: 0, medium: 0, low: 0 },
|
|
51
52
|
acceptedIds: [],
|
|
53
|
+
sourceStripFilesModified: 0,
|
|
54
|
+
sourceStripItemsApplied: 0,
|
|
55
|
+
sourceStripSkipped: [],
|
|
52
56
|
};
|
|
53
57
|
if (existsSync(inboxDir)) {
|
|
54
58
|
const entries = readdirSync(inboxDir, { withFileTypes: true });
|
|
@@ -99,6 +103,48 @@ export async function bulkAcceptObvious(args) {
|
|
|
99
103
|
catch {
|
|
100
104
|
/* best-effort */
|
|
101
105
|
}
|
|
106
|
+
// Source-comment derived DECs: replace the original essay
|
|
107
|
+
// block with `// §DEC-NNNN` so the file ends up carrying
|
|
108
|
+
// the bare cite, mirroring the §INV strip pass that 7b
|
|
109
|
+
// runs at adoption time.
|
|
110
|
+
const captureSource = stringField(stampedFm, "capture_source");
|
|
111
|
+
const blockId = stringField(stampedFm, "blockId");
|
|
112
|
+
if (captureSource === "init-source-comments" &&
|
|
113
|
+
blockId !== null) {
|
|
114
|
+
// Source files that came from Phase 7b are already dirty
|
|
115
|
+
// (the INV strip pass mutated them); runDecSourceStrip
|
|
116
|
+
// forces overwrite for the target file so the dirty
|
|
117
|
+
// check doesn't bail. Operator consented to source
|
|
118
|
+
// mutation at adoption time.
|
|
119
|
+
const stripOutcome = runDecSourceStrip({
|
|
120
|
+
repoRoot: args.repoRoot,
|
|
121
|
+
decId: id,
|
|
122
|
+
meta: {
|
|
123
|
+
blockId,
|
|
124
|
+
sourceFile: stringField(stampedFm, "sourceFile"),
|
|
125
|
+
captureSource,
|
|
126
|
+
title: stringField(stampedFm, "title"),
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
decResult.sourceStripFilesModified +=
|
|
130
|
+
stripOutcome.files_modified;
|
|
131
|
+
decResult.sourceStripItemsApplied +=
|
|
132
|
+
stripOutcome.items_applied;
|
|
133
|
+
if (stripOutcome.attempted &&
|
|
134
|
+
stripOutcome.items_applied === 0 &&
|
|
135
|
+
stripOutcome.reason !== undefined) {
|
|
136
|
+
decResult.sourceStripSkipped.push({
|
|
137
|
+
id,
|
|
138
|
+
reason: stripOutcome.reason,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
else if (!stripOutcome.attempted && stripOutcome.reason !== undefined) {
|
|
142
|
+
decResult.sourceStripSkipped.push({
|
|
143
|
+
id,
|
|
144
|
+
reason: stripOutcome.reason,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
102
148
|
}
|
|
103
149
|
decResult.decsAccepted += 1;
|
|
104
150
|
decResult.acceptedIds.push(id);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bulk-accept.js","sourceRoot":"","sources":["../../src/attention/bulk-accept.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EACL,UAAU,EACV,SAAS,EACT,YAAY,EACZ,WAAW,EAEX,MAAM,EACN,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,SAAS,IAAI,aAAa,EAAE,MAAM,MAAM,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,OAAO,EACL,aAAa,EACb,cAAc,GAEf,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"bulk-accept.js","sourceRoot":"","sources":["../../src/attention/bulk-accept.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic near-duplicate detection for DEC drafts.
|
|
3
|
+
*
|
|
4
|
+
* On a busy monorepo Phase 7b can emit several hundred DEC drafts; many
|
|
5
|
+
* are the same idea expressed in multiple files (a shared utility
|
|
6
|
+
* pattern documented identically across consumers, identical guard
|
|
7
|
+
* clauses repeated by file boundary, etc.). Dragging the operator
|
|
8
|
+
* through hundreds of per-draft triage prompts when a meaningful slice
|
|
9
|
+
* of them are obvious duplicates is a UX failure.
|
|
10
|
+
*
|
|
11
|
+
* This module clusters drafts by token-Jaccard similarity (no LLM calls,
|
|
12
|
+
* no network). Stopwords stripped, simple stem (drop trailing
|
|
13
|
+
* `s` / `ed` / `ing`), tokens >= 3 chars only. Two thresholds:
|
|
14
|
+
* - `>= 0.5` → definite duplicates → merge-by-default in the skill
|
|
15
|
+
* - `0.4–0.5` → potential duplicates → flagged for review
|
|
16
|
+
*
|
|
17
|
+
* Output is deliberately a pure data structure — the skill renders. The
|
|
18
|
+
* CLI subcommand wraps `findDuplicateClusters` for `--dry-run` previews.
|
|
19
|
+
*/
|
|
20
|
+
export interface DraftRef {
|
|
21
|
+
/** DEC id (e.g. `DEC-0042`). */
|
|
22
|
+
id: string;
|
|
23
|
+
/** Repo-relative path to the draft file in `_inbox/`. */
|
|
24
|
+
path: string;
|
|
25
|
+
/** Frontmatter title or fallback synthesized from filename. */
|
|
26
|
+
title: string;
|
|
27
|
+
/** Source file the draft was extracted from (frontmatter `sourceFile`). */
|
|
28
|
+
sourceFile: string;
|
|
29
|
+
/** capture_source frontmatter, e.g. `init-source-comments`. */
|
|
30
|
+
source: string;
|
|
31
|
+
/** capture_confidence frontmatter; null when unscored. */
|
|
32
|
+
confidence: string | null;
|
|
33
|
+
}
|
|
34
|
+
export interface DuplicateCluster {
|
|
35
|
+
/** Tier: `definite` (Jaccard >= 0.5) or `potential` (0.4..0.5). */
|
|
36
|
+
tier: "definite" | "potential";
|
|
37
|
+
/** Average pairwise Jaccard within the cluster. */
|
|
38
|
+
averageSimilarity: number;
|
|
39
|
+
/** Cluster members; first-listed is a stable suggested survivor (lowest id). */
|
|
40
|
+
drafts: DraftRef[];
|
|
41
|
+
}
|
|
42
|
+
export interface DedupResult {
|
|
43
|
+
/** Total drafts scanned (everything in `_inbox/` ending `.draft.md`). */
|
|
44
|
+
draftsScanned: number;
|
|
45
|
+
/** All clusters at Jaccard >= 0.4. Ordered: definite first, then potential, both by size desc. */
|
|
46
|
+
clusters: DuplicateCluster[];
|
|
47
|
+
/** Count of drafts inside a cluster. */
|
|
48
|
+
draftsInClusters: number;
|
|
49
|
+
/** Reducible = sum over clusters of (members - 1). */
|
|
50
|
+
reducible: number;
|
|
51
|
+
/** Threshold floor used for clustering (0.4 by default). */
|
|
52
|
+
thresholdFloor: number;
|
|
53
|
+
/** Definite-tier threshold (0.5 by default). */
|
|
54
|
+
thresholdDefinite: number;
|
|
55
|
+
}
|
|
56
|
+
/** Default tier thresholds — see module-level docstring. */
|
|
57
|
+
export declare const DEFAULT_THRESHOLD_DEFINITE = 0.5;
|
|
58
|
+
export declare const DEFAULT_THRESHOLD_FLOOR = 0.4;
|
|
59
|
+
/**
|
|
60
|
+
* Scan `.cairn/ground/decisions/_inbox/` for `.draft.md` files and
|
|
61
|
+
* return Jaccard-clustered duplicates at the two-tier thresholds. Pure
|
|
62
|
+
* deterministic — same input, same output.
|
|
63
|
+
*/
|
|
64
|
+
export declare function findDuplicateClusters(args: {
|
|
65
|
+
repoRoot: string;
|
|
66
|
+
thresholdFloor?: number;
|
|
67
|
+
thresholdDefinite?: number;
|
|
68
|
+
}): DedupResult;
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic near-duplicate detection for DEC drafts.
|
|
3
|
+
*
|
|
4
|
+
* On a busy monorepo Phase 7b can emit several hundred DEC drafts; many
|
|
5
|
+
* are the same idea expressed in multiple files (a shared utility
|
|
6
|
+
* pattern documented identically across consumers, identical guard
|
|
7
|
+
* clauses repeated by file boundary, etc.). Dragging the operator
|
|
8
|
+
* through hundreds of per-draft triage prompts when a meaningful slice
|
|
9
|
+
* of them are obvious duplicates is a UX failure.
|
|
10
|
+
*
|
|
11
|
+
* This module clusters drafts by token-Jaccard similarity (no LLM calls,
|
|
12
|
+
* no network). Stopwords stripped, simple stem (drop trailing
|
|
13
|
+
* `s` / `ed` / `ing`), tokens >= 3 chars only. Two thresholds:
|
|
14
|
+
* - `>= 0.5` → definite duplicates → merge-by-default in the skill
|
|
15
|
+
* - `0.4–0.5` → potential duplicates → flagged for review
|
|
16
|
+
*
|
|
17
|
+
* Output is deliberately a pure data structure — the skill renders. The
|
|
18
|
+
* CLI subcommand wraps `findDuplicateClusters` for `--dry-run` previews.
|
|
19
|
+
*/
|
|
20
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
import { decisionsDir } from "../ground/paths.js";
|
|
23
|
+
/* -------------------------------------------------------------------------- */
|
|
24
|
+
/* Tokenization */
|
|
25
|
+
/* -------------------------------------------------------------------------- */
|
|
26
|
+
/**
|
|
27
|
+
* Stopwords. Hard-coded English set plus a handful of cairn-domain
|
|
28
|
+
* terms that appear in nearly every draft and would otherwise dominate
|
|
29
|
+
* Jaccard scores ("rationale", "decision").
|
|
30
|
+
*/
|
|
31
|
+
const STOPWORDS = new Set([
|
|
32
|
+
"the", "a", "an", "and", "or", "of", "in", "on", "to", "for", "with",
|
|
33
|
+
"by", "from", "is", "are", "was", "were", "be", "been", "being", "has",
|
|
34
|
+
"have", "had", "do", "does", "did", "this", "that", "these", "those",
|
|
35
|
+
"it", "its", "as", "at", "but", "if", "than", "so", "use", "used",
|
|
36
|
+
"using", "via", "out", "off", "up", "our", "their", "one", "two",
|
|
37
|
+
"when", "where", "what", "how", "why", "who", "which", "can", "should",
|
|
38
|
+
"must", "will", "shall", "may", "not", "no", "any", "all", "some",
|
|
39
|
+
"few", "more", "most", "only", "also",
|
|
40
|
+
]);
|
|
41
|
+
/** Default char window of body to fold into the token bag. */
|
|
42
|
+
const BODY_CHAR_WINDOW = 500;
|
|
43
|
+
function stem(w) {
|
|
44
|
+
if (w.length <= 4)
|
|
45
|
+
return w;
|
|
46
|
+
if (w.endsWith("ing"))
|
|
47
|
+
return w.slice(0, -3);
|
|
48
|
+
if (w.endsWith("ed"))
|
|
49
|
+
return w.slice(0, -2);
|
|
50
|
+
if (w.endsWith("s") && !w.endsWith("ss"))
|
|
51
|
+
return w.slice(0, -1);
|
|
52
|
+
return w;
|
|
53
|
+
}
|
|
54
|
+
function tokenize(text) {
|
|
55
|
+
return new Set(text
|
|
56
|
+
.toLowerCase()
|
|
57
|
+
.replace(/[^a-z0-9\s]/g, " ")
|
|
58
|
+
.split(/\s+/)
|
|
59
|
+
.map(stem)
|
|
60
|
+
.filter((w) => w.length >= 3 && !STOPWORDS.has(w)));
|
|
61
|
+
}
|
|
62
|
+
function jaccard(a, b) {
|
|
63
|
+
if (a.size === 0 && b.size === 0)
|
|
64
|
+
return 0;
|
|
65
|
+
let inter = 0;
|
|
66
|
+
for (const x of a)
|
|
67
|
+
if (b.has(x))
|
|
68
|
+
inter += 1;
|
|
69
|
+
const union = a.size + b.size - inter;
|
|
70
|
+
return union === 0 ? 0 : inter / union;
|
|
71
|
+
}
|
|
72
|
+
function parseMinimalFrontmatter(raw) {
|
|
73
|
+
const m = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
74
|
+
if (m === null || m[1] === undefined || m[2] === undefined) {
|
|
75
|
+
return { fm: {}, body: raw };
|
|
76
|
+
}
|
|
77
|
+
const fm = {};
|
|
78
|
+
for (const line of m[1].split("\n")) {
|
|
79
|
+
const lm = line.match(/^(\w[\w-]*):\s*(.*)$/);
|
|
80
|
+
if (lm === null || lm[1] === undefined || lm[2] === undefined)
|
|
81
|
+
continue;
|
|
82
|
+
const key = lm[1];
|
|
83
|
+
const val = lm[2].trim().replace(/^["']|["']$/g, "");
|
|
84
|
+
if (key === "id" || key === "title" || key === "sourceFile" || key === "capture_source" || key === "capture_confidence") {
|
|
85
|
+
fm[key] = val;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return { fm, body: m[2] };
|
|
89
|
+
}
|
|
90
|
+
/** Default tier thresholds — see module-level docstring. */
|
|
91
|
+
export const DEFAULT_THRESHOLD_DEFINITE = 0.5;
|
|
92
|
+
export const DEFAULT_THRESHOLD_FLOOR = 0.4;
|
|
93
|
+
/**
|
|
94
|
+
* Scan `.cairn/ground/decisions/_inbox/` for `.draft.md` files and
|
|
95
|
+
* return Jaccard-clustered duplicates at the two-tier thresholds. Pure
|
|
96
|
+
* deterministic — same input, same output.
|
|
97
|
+
*/
|
|
98
|
+
export function findDuplicateClusters(args) {
|
|
99
|
+
const thresholdFloor = args.thresholdFloor ?? DEFAULT_THRESHOLD_FLOOR;
|
|
100
|
+
const thresholdDefinite = args.thresholdDefinite ?? DEFAULT_THRESHOLD_DEFINITE;
|
|
101
|
+
const inbox = join(decisionsDir(args.repoRoot), "_inbox");
|
|
102
|
+
if (!existsSync(inbox)) {
|
|
103
|
+
return {
|
|
104
|
+
draftsScanned: 0,
|
|
105
|
+
clusters: [],
|
|
106
|
+
draftsInClusters: 0,
|
|
107
|
+
reducible: 0,
|
|
108
|
+
thresholdFloor,
|
|
109
|
+
thresholdDefinite,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const files = readdirSync(inbox, { encoding: "utf8" }).filter((f) => f.endsWith(".draft.md"));
|
|
113
|
+
const docs = [];
|
|
114
|
+
for (const f of files) {
|
|
115
|
+
let raw;
|
|
116
|
+
try {
|
|
117
|
+
raw = readFileSync(join(inbox, f), "utf8");
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const { fm, body } = parseMinimalFrontmatter(raw);
|
|
123
|
+
const id = fm.id ?? f.replace(/\.draft\.md$/, "");
|
|
124
|
+
const title = fm.title ?? id;
|
|
125
|
+
const text = `${title} ${body.slice(0, BODY_CHAR_WINDOW)}`;
|
|
126
|
+
docs.push({
|
|
127
|
+
id,
|
|
128
|
+
path: `.cairn/ground/decisions/_inbox/${f}`,
|
|
129
|
+
title,
|
|
130
|
+
sourceFile: fm.sourceFile ?? "",
|
|
131
|
+
source: fm.capture_source ?? "",
|
|
132
|
+
confidence: fm.capture_confidence ?? null,
|
|
133
|
+
tokens: tokenize(text),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
const n = docs.length;
|
|
137
|
+
if (n === 0) {
|
|
138
|
+
return {
|
|
139
|
+
draftsScanned: 0,
|
|
140
|
+
clusters: [],
|
|
141
|
+
draftsInClusters: 0,
|
|
142
|
+
reducible: 0,
|
|
143
|
+
thresholdFloor,
|
|
144
|
+
thresholdDefinite,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
// Union-find for cluster merging at the floor threshold; track per-edge
|
|
148
|
+
// similarities so we can compute the cluster average + tier.
|
|
149
|
+
const parent = Array.from({ length: n }, (_, i) => i);
|
|
150
|
+
const find = (x) => {
|
|
151
|
+
let cur = x;
|
|
152
|
+
while (parent[cur] !== cur) {
|
|
153
|
+
parent[cur] = parent[parent[cur] ?? cur] ?? cur;
|
|
154
|
+
cur = parent[cur] ?? cur;
|
|
155
|
+
}
|
|
156
|
+
return cur;
|
|
157
|
+
};
|
|
158
|
+
const unite = (a, b) => {
|
|
159
|
+
const ra = find(a);
|
|
160
|
+
const rb = find(b);
|
|
161
|
+
if (ra !== rb)
|
|
162
|
+
parent[ra] = rb;
|
|
163
|
+
};
|
|
164
|
+
const edgeSims = [];
|
|
165
|
+
for (let i = 0; i < n; i++) {
|
|
166
|
+
const di = docs[i];
|
|
167
|
+
if (di === undefined)
|
|
168
|
+
continue;
|
|
169
|
+
for (let j = i + 1; j < n; j++) {
|
|
170
|
+
const dj = docs[j];
|
|
171
|
+
if (dj === undefined)
|
|
172
|
+
continue;
|
|
173
|
+
const sim = jaccard(di.tokens, dj.tokens);
|
|
174
|
+
if (sim >= thresholdFloor) {
|
|
175
|
+
edgeSims.push({ a: i, b: j, sim });
|
|
176
|
+
unite(i, j);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const groupIndex = new Map();
|
|
181
|
+
for (let i = 0; i < n; i++) {
|
|
182
|
+
const r = find(i);
|
|
183
|
+
let bucket = groupIndex.get(r);
|
|
184
|
+
if (bucket === undefined) {
|
|
185
|
+
bucket = [];
|
|
186
|
+
groupIndex.set(r, bucket);
|
|
187
|
+
}
|
|
188
|
+
bucket.push(i);
|
|
189
|
+
}
|
|
190
|
+
const clusters = [];
|
|
191
|
+
let draftsInClusters = 0;
|
|
192
|
+
let reducible = 0;
|
|
193
|
+
for (const idxs of groupIndex.values()) {
|
|
194
|
+
if (idxs.length < 2)
|
|
195
|
+
continue;
|
|
196
|
+
// Average similarity = mean of edges within the cluster (over all
|
|
197
|
+
// pairs, not just MST edges) for a stable tier signal.
|
|
198
|
+
let total = 0;
|
|
199
|
+
let count = 0;
|
|
200
|
+
const set = new Set(idxs);
|
|
201
|
+
for (const e of edgeSims) {
|
|
202
|
+
if (set.has(e.a) && set.has(e.b)) {
|
|
203
|
+
total += e.sim;
|
|
204
|
+
count += 1;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const avg = count === 0 ? 0 : total / count;
|
|
208
|
+
const tier = avg >= thresholdDefinite ? "definite" : "potential";
|
|
209
|
+
// Stable survivor pick: lowest DEC id (lex sort works for DEC-NNNN).
|
|
210
|
+
const ordered = idxs
|
|
211
|
+
.map((i) => docs[i])
|
|
212
|
+
.filter((d) => d !== undefined);
|
|
213
|
+
ordered.sort((a, b) => a.id.localeCompare(b.id));
|
|
214
|
+
clusters.push({
|
|
215
|
+
tier,
|
|
216
|
+
averageSimilarity: Number(avg.toFixed(3)),
|
|
217
|
+
drafts: ordered.map(({ tokens: _omit, ...rest }) => rest),
|
|
218
|
+
});
|
|
219
|
+
draftsInClusters += idxs.length;
|
|
220
|
+
reducible += idxs.length - 1;
|
|
221
|
+
}
|
|
222
|
+
// Definite tier first, both ordered by size desc, then avg sim desc.
|
|
223
|
+
clusters.sort((a, b) => {
|
|
224
|
+
if (a.tier !== b.tier)
|
|
225
|
+
return a.tier === "definite" ? -1 : 1;
|
|
226
|
+
if (a.drafts.length !== b.drafts.length)
|
|
227
|
+
return b.drafts.length - a.drafts.length;
|
|
228
|
+
return b.averageSimilarity - a.averageSimilarity;
|
|
229
|
+
});
|
|
230
|
+
return {
|
|
231
|
+
draftsScanned: n,
|
|
232
|
+
clusters,
|
|
233
|
+
draftsInClusters,
|
|
234
|
+
reducible,
|
|
235
|
+
thresholdFloor,
|
|
236
|
+
thresholdDefinite,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=dedup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dedup.js","sourceRoot":"","sources":["../../src/attention/dedup.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
export { scoreDecDraft, scoreInvariant, type DraftConfidence, type DraftScoreInput, } from "./scoring.js";
|
|
2
2
|
export { bulkAcceptObvious, type BulkAcceptArgs, type BulkAcceptResult, } from "./bulk-accept.js";
|
|
3
|
+
export { findDuplicateClusters, DEFAULT_THRESHOLD_DEFINITE, DEFAULT_THRESHOLD_FLOOR, type DraftRef, type DuplicateCluster, type DedupResult, } from "./dedup.js";
|
|
4
|
+
export { parseDraftMeta, findLatestSourceCommentsAudit, runDecSourceStrip, type DraftMeta, type StripOutcomeSummary, } from "./source-strip.js";
|
|
5
|
+
export { restoreDec, type RestoreArgs, type RestoreResult, type RestoreState, } from "./restore.js";
|
package/dist/attention/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
export { scoreDecDraft, scoreInvariant, } from "./scoring.js";
|
|
2
2
|
export { bulkAcceptObvious, } from "./bulk-accept.js";
|
|
3
|
+
export { findDuplicateClusters, DEFAULT_THRESHOLD_DEFINITE, DEFAULT_THRESHOLD_FLOOR, } from "./dedup.js";
|
|
4
|
+
export { parseDraftMeta, findLatestSourceCommentsAudit, runDecSourceStrip, } from "./source-strip.js";
|
|
5
|
+
export { restoreDec, } from "./restore.js";
|
|
3
6
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/attention/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,cAAc,GAGf,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,iBAAiB,GAGlB,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/attention/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,cAAc,GAGf,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,iBAAiB,GAGlB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,qBAAqB,EACrB,0BAA0B,EAC1B,uBAAuB,GAIxB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,cAAc,EACd,6BAA6B,EAC7B,iBAAiB,GAGlB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,UAAU,GAIX,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Restore a previously rejected or accepted DEC back to draft state so
|
|
3
|
+
* the operator can re-evaluate it.
|
|
4
|
+
*
|
|
5
|
+
* Two restore paths:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Rejected → draft.** The DEC sits at
|
|
8
|
+
* `.cairn/ground/decisions/_inbox/<id>.rejected.md`. Move it back
|
|
9
|
+
* to `.draft.md` and flip `status: rejected` → the original
|
|
10
|
+
* `draft-from-source-comment` (or `draft-from-init-docs` etc.) so
|
|
11
|
+
* `cairn_resolve_attention` will find it again. No source
|
|
12
|
+
* mutation — rejected drafts never strip-replaced source, so
|
|
13
|
+
* there's nothing to reverse.
|
|
14
|
+
*
|
|
15
|
+
* 2. **Accepted → draft.** The DEC sits at
|
|
16
|
+
* `.cairn/ground/decisions/<id>.md`. Move it to
|
|
17
|
+
* `_inbox/<id>.draft.md`, flip `status: accepted` → the original
|
|
18
|
+
* draft status, and rebuild the decisions ledger so the canonical
|
|
19
|
+
* surface no longer resolves the id. Source files keep the
|
|
20
|
+
* `// §DEC-NNNN` cite (we don't reverse-strip — the cite is a
|
|
21
|
+
* durable artifact of the prior accept and removing it without
|
|
22
|
+
* operator intent risks dropping a legit DEC reference if the
|
|
23
|
+
* operator never re-accepts). Operator can re-accept (idempotent
|
|
24
|
+
* via the `already-stripped` check) or run a manual edit.
|
|
25
|
+
*
|
|
26
|
+
* The id is reserved across both paths — it stays out of the high-water
|
|
27
|
+
* mark per `scanExistingDecisionIds`.
|
|
28
|
+
*/
|
|
29
|
+
export type RestoreState = "rejected" | "accepted" | "draft" | "not-found";
|
|
30
|
+
export interface RestoreArgs {
|
|
31
|
+
repoRoot: string;
|
|
32
|
+
decId: string;
|
|
33
|
+
}
|
|
34
|
+
export interface RestoreResult {
|
|
35
|
+
ok: boolean;
|
|
36
|
+
decId: string;
|
|
37
|
+
priorState: RestoreState;
|
|
38
|
+
/** Repo-relative path the draft now lives at, or null when no move happened. */
|
|
39
|
+
draftPath: string | null;
|
|
40
|
+
/** Set when ok is false. */
|
|
41
|
+
reason?: string;
|
|
42
|
+
}
|
|
43
|
+
export declare function restoreDec(args: RestoreArgs): Promise<RestoreResult>;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Restore a previously rejected or accepted DEC back to draft state so
|
|
3
|
+
* the operator can re-evaluate it.
|
|
4
|
+
*
|
|
5
|
+
* Two restore paths:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Rejected → draft.** The DEC sits at
|
|
8
|
+
* `.cairn/ground/decisions/_inbox/<id>.rejected.md`. Move it back
|
|
9
|
+
* to `.draft.md` and flip `status: rejected` → the original
|
|
10
|
+
* `draft-from-source-comment` (or `draft-from-init-docs` etc.) so
|
|
11
|
+
* `cairn_resolve_attention` will find it again. No source
|
|
12
|
+
* mutation — rejected drafts never strip-replaced source, so
|
|
13
|
+
* there's nothing to reverse.
|
|
14
|
+
*
|
|
15
|
+
* 2. **Accepted → draft.** The DEC sits at
|
|
16
|
+
* `.cairn/ground/decisions/<id>.md`. Move it to
|
|
17
|
+
* `_inbox/<id>.draft.md`, flip `status: accepted` → the original
|
|
18
|
+
* draft status, and rebuild the decisions ledger so the canonical
|
|
19
|
+
* surface no longer resolves the id. Source files keep the
|
|
20
|
+
* `// §DEC-NNNN` cite (we don't reverse-strip — the cite is a
|
|
21
|
+
* durable artifact of the prior accept and removing it without
|
|
22
|
+
* operator intent risks dropping a legit DEC reference if the
|
|
23
|
+
* operator never re-accepts). Operator can re-accept (idempotent
|
|
24
|
+
* via the `already-stripped` check) or run a manual edit.
|
|
25
|
+
*
|
|
26
|
+
* The id is reserved across both paths — it stays out of the high-water
|
|
27
|
+
* mark per `scanExistingDecisionIds`.
|
|
28
|
+
*/
|
|
29
|
+
import { existsSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
|
|
30
|
+
import { join } from "node:path";
|
|
31
|
+
import { decisionsDir } from "../ground/paths.js";
|
|
32
|
+
import { writeDecisionsLedger } from "../ground/ledgers.js";
|
|
33
|
+
import { withWriteLock } from "../lock.js";
|
|
34
|
+
import { logger } from "../logger.js";
|
|
35
|
+
const log = logger("attention.restore");
|
|
36
|
+
const DRAFT_STATUS_DEFAULT = "draft-from-source-comment";
|
|
37
|
+
/**
|
|
38
|
+
* Walk the body for a `status:` frontmatter line and replace the value
|
|
39
|
+
* with the default draft status. Also resets `decided_at` / `decided_by`
|
|
40
|
+
* fields so the draft looks fresh, which mirrors how Phase 7b writes
|
|
41
|
+
* brand-new drafts.
|
|
42
|
+
*/
|
|
43
|
+
function flipStatusToDraft(body) {
|
|
44
|
+
return body.replace(/^status:\s*(?:accepted|rejected)\b/m, `status: ${DRAFT_STATUS_DEFAULT}`);
|
|
45
|
+
}
|
|
46
|
+
export async function restoreDec(args) {
|
|
47
|
+
if (!/^DEC-\d{4,}$/.test(args.decId)) {
|
|
48
|
+
return {
|
|
49
|
+
ok: false,
|
|
50
|
+
decId: args.decId,
|
|
51
|
+
priorState: "not-found",
|
|
52
|
+
draftPath: null,
|
|
53
|
+
reason: "invalid-id",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const decDir = decisionsDir(args.repoRoot);
|
|
57
|
+
const inboxDir = join(decDir, "_inbox");
|
|
58
|
+
const rejectedPath = join(inboxDir, `${args.decId}.rejected.md`);
|
|
59
|
+
const draftPath = join(inboxDir, `${args.decId}.draft.md`);
|
|
60
|
+
const acceptedPath = join(decDir, `${args.decId}.md`);
|
|
61
|
+
const draftRel = `.cairn/ground/decisions/_inbox/${args.decId}.draft.md`;
|
|
62
|
+
if (existsSync(draftPath)) {
|
|
63
|
+
return {
|
|
64
|
+
ok: true,
|
|
65
|
+
decId: args.decId,
|
|
66
|
+
priorState: "draft",
|
|
67
|
+
draftPath: draftRel,
|
|
68
|
+
reason: "already-draft",
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const isRejected = existsSync(rejectedPath);
|
|
72
|
+
const isAccepted = existsSync(acceptedPath);
|
|
73
|
+
if (!isRejected && !isAccepted) {
|
|
74
|
+
return {
|
|
75
|
+
ok: false,
|
|
76
|
+
decId: args.decId,
|
|
77
|
+
priorState: "not-found",
|
|
78
|
+
draftPath: null,
|
|
79
|
+
reason: "not-found",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return withWriteLock(args.repoRoot, () => {
|
|
83
|
+
if (isRejected) {
|
|
84
|
+
const body = readFileSync(rejectedPath, "utf8");
|
|
85
|
+
const restored = flipStatusToDraft(body);
|
|
86
|
+
writeFileSync(draftPath, restored, "utf8");
|
|
87
|
+
try {
|
|
88
|
+
rmSync(rejectedPath, { force: true });
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
/* best-effort */
|
|
92
|
+
}
|
|
93
|
+
log.info({ decId: args.decId }, "restored rejected DEC to draft");
|
|
94
|
+
return {
|
|
95
|
+
ok: true,
|
|
96
|
+
decId: args.decId,
|
|
97
|
+
priorState: "rejected",
|
|
98
|
+
draftPath: draftRel,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// Accepted → draft. Source cite stays in place; see module docstring
|
|
102
|
+
// for the rationale.
|
|
103
|
+
const body = readFileSync(acceptedPath, "utf8");
|
|
104
|
+
const restored = flipStatusToDraft(body);
|
|
105
|
+
writeFileSync(draftPath, restored, "utf8");
|
|
106
|
+
try {
|
|
107
|
+
rmSync(acceptedPath, { force: true });
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
/* best-effort */
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
writeDecisionsLedger({ repoRoot: args.repoRoot });
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
log.warn({ err: err instanceof Error ? err.message : String(err) }, "decisions ledger rebuild failed after accepted-to-draft restore");
|
|
117
|
+
}
|
|
118
|
+
log.info({ decId: args.decId }, "restored accepted DEC to draft (source cite kept)");
|
|
119
|
+
return {
|
|
120
|
+
ok: true,
|
|
121
|
+
decId: args.decId,
|
|
122
|
+
priorState: "accepted",
|
|
123
|
+
draftPath: draftRel,
|
|
124
|
+
};
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=restore.js.map
|