ctxr-cli 0.1.0 → 0.2.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/adapters/forge/github.d.ts +4 -1
- package/dist/adapters/forge/github.d.ts.map +1 -1
- package/dist/adapters/forge/github.js +41 -1
- package/dist/adapters/forge/github.js.map +1 -1
- package/dist/adapters/forge/types.d.ts +23 -0
- package/dist/adapters/forge/types.d.ts.map +1 -1
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/adapters/types.js +2 -1
- package/dist/adapters/types.js.map +1 -1
- package/dist/commands/check.d.ts +12 -4
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +28 -1
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/entry-append.d.ts +25 -0
- package/dist/commands/entry-append.d.ts.map +1 -0
- package/dist/commands/entry-append.js +39 -0
- package/dist/commands/entry-append.js.map +1 -0
- package/dist/commands/graph-build.d.ts +5 -0
- package/dist/commands/graph-build.d.ts.map +1 -1
- package/dist/commands/graph-build.js +8 -5
- package/dist/commands/graph-build.js.map +1 -1
- package/dist/commands/graph-query.d.ts +15 -0
- package/dist/commands/graph-query.d.ts.map +1 -1
- package/dist/commands/graph-query.js +12 -2
- package/dist/commands/graph-query.js.map +1 -1
- package/dist/commands/identity.d.ts +24 -0
- package/dist/commands/identity.d.ts.map +1 -0
- package/dist/commands/identity.js +35 -0
- package/dist/commands/identity.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +7 -7
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/rollup-stale.d.ts +18 -0
- package/dist/commands/rollup-stale.d.ts.map +1 -0
- package/dist/commands/rollup-stale.js +35 -0
- package/dist/commands/rollup-stale.js.map +1 -0
- package/dist/commands/rollup-write.d.ts +9 -1
- package/dist/commands/rollup-write.d.ts.map +1 -1
- package/dist/commands/rollup-write.js +17 -2
- package/dist/commands/rollup-write.js.map +1 -1
- package/dist/commands/session-capture.d.ts +25 -0
- package/dist/commands/session-capture.d.ts.map +1 -0
- package/dist/commands/session-capture.js +119 -0
- package/dist/commands/session-capture.js.map +1 -0
- package/dist/commands/session-land.d.ts +45 -0
- package/dist/commands/session-land.d.ts.map +1 -0
- package/dist/commands/session-land.js +140 -0
- package/dist/commands/session-land.js.map +1 -0
- package/dist/commands/session-list.d.ts.map +1 -1
- package/dist/commands/session-list.js +3 -2
- package/dist/commands/session-list.js.map +1 -1
- package/dist/commands/session-reap.d.ts.map +1 -1
- package/dist/commands/session-reap.js +3 -2
- package/dist/commands/session-reap.js.map +1 -1
- package/dist/commands/session-submit.d.ts +2 -0
- package/dist/commands/session-submit.d.ts.map +1 -1
- package/dist/commands/session-submit.js +6 -2
- package/dist/commands/session-submit.js.map +1 -1
- package/dist/commands/source-add-alt.d.ts +20 -0
- package/dist/commands/source-add-alt.d.ts.map +1 -0
- package/dist/commands/source-add-alt.js +48 -0
- package/dist/commands/source-add-alt.js.map +1 -0
- package/dist/commands/source-check.js +1 -1
- package/dist/commands/source-check.js.map +1 -1
- package/dist/commands/source-stamp.d.ts +24 -0
- package/dist/commands/source-stamp.d.ts.map +1 -0
- package/dist/commands/source-stamp.js +46 -0
- package/dist/commands/source-stamp.js.map +1 -0
- package/dist/commands/verify.js +2 -2
- package/dist/commands/verify.js.map +1 -1
- package/dist/config/defaults.d.ts +13 -0
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +13 -0
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/schema.d.ts +31 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +42 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/core/agents-doc.d.ts.map +1 -1
- package/dist/core/agents-doc.js +3 -1
- package/dist/core/agents-doc.js.map +1 -1
- package/dist/core/checks/disclosure-checks.d.ts +4 -0
- package/dist/core/checks/disclosure-checks.d.ts.map +1 -0
- package/dist/core/checks/disclosure-checks.js +24 -0
- package/dist/core/checks/disclosure-checks.js.map +1 -0
- package/dist/core/checks/identity-checks.d.ts +6 -1
- package/dist/core/checks/identity-checks.d.ts.map +1 -1
- package/dist/core/checks/identity-checks.js +23 -19
- package/dist/core/checks/identity-checks.js.map +1 -1
- package/dist/core/checks/integrity-checks.js +2 -2
- package/dist/core/checks/integrity-checks.js.map +1 -1
- package/dist/core/checks/manifest.d.ts.map +1 -1
- package/dist/core/checks/manifest.js +2 -0
- package/dist/core/checks/manifest.js.map +1 -1
- package/dist/core/checks/organize-checks.d.ts +2 -0
- package/dist/core/checks/organize-checks.d.ts.map +1 -1
- package/dist/core/checks/organize-checks.js +24 -1
- package/dist/core/checks/organize-checks.js.map +1 -1
- package/dist/core/checks/write-lifecycle-checks.d.ts +4 -1
- package/dist/core/checks/write-lifecycle-checks.d.ts.map +1 -1
- package/dist/core/checks/write-lifecycle-checks.js +18 -2
- package/dist/core/checks/write-lifecycle-checks.js.map +1 -1
- package/dist/core/disclosure/leak-scan.d.ts +19 -0
- package/dist/core/disclosure/leak-scan.d.ts.map +1 -0
- package/dist/core/disclosure/leak-scan.js +31 -0
- package/dist/core/disclosure/leak-scan.js.map +1 -0
- package/dist/core/errors.d.ts +40 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +128 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/fs/fenced-region.d.ts +9 -0
- package/dist/core/fs/fenced-region.d.ts.map +1 -1
- package/dist/core/fs/fenced-region.js +40 -0
- package/dist/core/fs/fenced-region.js.map +1 -1
- package/dist/core/git/repo.d.ts +2 -0
- package/dist/core/git/repo.d.ts.map +1 -1
- package/dist/core/git/repo.js +4 -0
- package/dist/core/git/repo.js.map +1 -1
- package/dist/core/graph/document.d.ts +26 -0
- package/dist/core/graph/document.d.ts.map +1 -0
- package/dist/core/graph/document.js +67 -0
- package/dist/core/graph/document.js.map +1 -0
- package/dist/core/graph/model.d.ts +44 -2
- package/dist/core/graph/model.d.ts.map +1 -1
- package/dist/core/graph/model.js +65 -6
- package/dist/core/graph/model.js.map +1 -1
- package/dist/core/graph/persist.d.ts +4 -0
- package/dist/core/graph/persist.d.ts.map +1 -1
- package/dist/core/graph/persist.js +10 -0
- package/dist/core/graph/persist.js.map +1 -1
- package/dist/core/graph/query.d.ts +24 -0
- package/dist/core/graph/query.d.ts.map +1 -1
- package/dist/core/graph/query.js +45 -2
- package/dist/core/graph/query.js.map +1 -1
- package/dist/core/identity.d.ts +36 -4
- package/dist/core/identity.d.ts.map +1 -1
- package/dist/core/identity.js +119 -12
- package/dist/core/identity.js.map +1 -1
- package/dist/core/ingest/canonical-url.d.ts +11 -0
- package/dist/core/ingest/canonical-url.d.ts.map +1 -0
- package/dist/core/ingest/canonical-url.js +27 -0
- package/dist/core/ingest/canonical-url.js.map +1 -0
- package/dist/core/ingest/identity.d.ts +2 -0
- package/dist/core/ingest/identity.d.ts.map +1 -1
- package/dist/core/ingest/identity.js +2 -0
- package/dist/core/ingest/identity.js.map +1 -1
- package/dist/core/ingest/model.d.ts +9 -2
- package/dist/core/ingest/model.d.ts.map +1 -1
- package/dist/core/ingest/model.js +32 -3
- package/dist/core/ingest/model.js.map +1 -1
- package/dist/core/procedures.d.ts +32 -12
- package/dist/core/procedures.d.ts.map +1 -1
- package/dist/core/procedures.js +613 -75
- package/dist/core/procedures.js.map +1 -1
- package/dist/core/rollup.d.ts +29 -0
- package/dist/core/rollup.d.ts.map +1 -0
- package/dist/core/rollup.js +60 -0
- package/dist/core/rollup.js.map +1 -0
- package/dist/core/session.d.ts +14 -0
- package/dist/core/session.d.ts.map +1 -1
- package/dist/core/session.js +19 -0
- package/dist/core/session.js.map +1 -1
- package/dist/core/write-lifecycle/path-gate.d.ts +20 -0
- package/dist/core/write-lifecycle/path-gate.d.ts.map +1 -0
- package/dist/core/write-lifecycle/path-gate.js +90 -0
- package/dist/core/write-lifecycle/path-gate.js.map +1 -0
- package/dist/prompt/inquirer-prompter.d.ts.map +1 -1
- package/dist/prompt/inquirer-prompter.js +4 -1
- package/dist/prompt/inquirer-prompter.js.map +1 -1
- package/dist/prompt/prompter.d.ts +9 -0
- package/dist/prompt/prompter.d.ts.map +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +149 -4
- package/dist/run.js.map +1 -1
- package/package.json +1 -1
package/dist/core/procedures.js
CHANGED
|
@@ -2,86 +2,624 @@ import { mkdir, readdir, readFile, rm } from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { scanDocsDir, SKILL_FILE_NAME } from './conventions.js';
|
|
4
4
|
import { writeFileAtomic } from './fs/atomic.js';
|
|
5
|
+
import { GRAPH_DOCUMENT_RELATIVE_PATH } from './graph/persist.js';
|
|
6
|
+
import { identityFilePath } from './identity.js';
|
|
5
7
|
export const MANAGED_SKILL_HEADER = '<!-- Owned by contexture — written by `ctxr init`, refreshed by `ctxr update`. Do not edit; add your own skills alongside. -->';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
`,
|
|
8
|
+
/**
|
|
9
|
+
* owned-skills-expansion D3: the placement procedure's termination test is
|
|
10
|
+
* emitted only for a layer whose configured description implies an end
|
|
11
|
+
* state; a layer whose description describes retired items is the
|
|
12
|
+
* destination `ctxr archive` moves things to. Both are read off the
|
|
13
|
+
* description text, never off a layer's name.
|
|
14
|
+
*/
|
|
15
|
+
const END_STATE_PATTERN = /\b(end[- ]state|finish(?:ed|es|ing)?|finish line|deadline|due date)\b/i;
|
|
16
|
+
const RETIRED_PATTERN = /\b(completed|abandoned|inactive|archived|retired|dormant)\b/i;
|
|
17
|
+
export function terminatingLayers(config) {
|
|
18
|
+
return config.taxonomy.layers.filter((layer) => END_STATE_PATTERN.test(layer.description) && !RETIRED_PATTERN.test(layer.description));
|
|
19
|
+
}
|
|
20
|
+
export function retiredLayers(config) {
|
|
21
|
+
return config.taxonomy.layers.filter((layer) => RETIRED_PATTERN.test(layer.description));
|
|
22
|
+
}
|
|
23
|
+
function skillDocument(seed, config) {
|
|
24
|
+
const lines = [
|
|
25
|
+
'---',
|
|
26
|
+
`name: ${seed.file}`,
|
|
27
|
+
`description: ${seed.description}`,
|
|
28
|
+
'---',
|
|
29
|
+
'',
|
|
30
|
+
MANAGED_SKILL_HEADER,
|
|
31
|
+
'',
|
|
32
|
+
`# ${seed.name}`,
|
|
33
|
+
'',
|
|
34
|
+
...seed.body(config),
|
|
35
|
+
];
|
|
36
|
+
return `${lines.join('\n')}\n`;
|
|
37
|
+
}
|
|
38
|
+
function joinNames(layers) {
|
|
39
|
+
return layers.map((layer) => `**${layer.name}**`).join(' / ');
|
|
40
|
+
}
|
|
41
|
+
function placementLayerStep(config) {
|
|
42
|
+
const { layers } = config.taxonomy;
|
|
43
|
+
if (layers.length === 0) {
|
|
44
|
+
return [
|
|
45
|
+
'## 1. Which layer?',
|
|
46
|
+
'',
|
|
47
|
+
'This store declares no top-level layers, so there is no layer decision: place the note next to the notes',
|
|
48
|
+
'it will link to most (`ctxr graph query neighbors <path>` on the closest existing note shows that cluster)',
|
|
49
|
+
'and let wikilinks carry the structure.',
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
const lines = [
|
|
53
|
+
'## 1. Which layer?',
|
|
54
|
+
'',
|
|
55
|
+
'Read AGENTS.md\'s "Placing a new note" section — it lists this store\'s configured layers with their',
|
|
56
|
+
'descriptions and their visibility defaults. Choose by what the content fundamentally IS, not by surface',
|
|
57
|
+
'keywords; a label on a location says nothing about what is actually kept there.',
|
|
58
|
+
];
|
|
59
|
+
const terminating = terminatingLayers(config);
|
|
60
|
+
if (terminating.length > 0) {
|
|
61
|
+
lines.push('', `- Termination test for ${joinNames(terminating)}: does this have a finish line? If it never closes, it does`, ' not belong there. An item in that layer that can never close is a smell — it either clutters the layer', ' as open-forever or gets retired prematurely. Never create one on purpose; an ongoing responsibility', ' with a standard to maintain belongs in a layer whose description says so.');
|
|
62
|
+
}
|
|
63
|
+
const retired = retiredLayers(config);
|
|
64
|
+
if (retired.length > 0) {
|
|
65
|
+
lines.push('', `- ${joinNames(retired)} is where the other layers' finished or dropped items go — never a first placement.`, ' Retire a note with `ctxr archive <path>` (a tracked move; frontmatter untouched).');
|
|
66
|
+
}
|
|
67
|
+
lines.push('', 'If no layer fits, use the catch-all location that section names and revisit the placement later.');
|
|
68
|
+
return lines;
|
|
69
|
+
}
|
|
70
|
+
const PLACEMENT = {
|
|
71
|
+
file: 'ctxr-placement',
|
|
72
|
+
name: 'Placement',
|
|
73
|
+
description: 'Choose the right taxonomy layer, location, and visibility for a new or relocated note in this contexture store, with the reasoning.',
|
|
74
|
+
body: (config) => [
|
|
75
|
+
'Decide where a note lives BEFORE writing it, and say why: the caller wants the reasoning (which layer, why',
|
|
76
|
+
'this location over its sibling, which visibility, what would promote it) so they can push back — not a',
|
|
77
|
+
'folder name. Answer the questions in order; stop early only when the content clearly resolves.',
|
|
78
|
+
'',
|
|
79
|
+
...placementLayerStep(config),
|
|
80
|
+
'',
|
|
81
|
+
'## 2. Which location within the layer?',
|
|
82
|
+
'',
|
|
83
|
+
'- Match on what the content IS. When two sibling locations both seem to fit, name the distinction between',
|
|
84
|
+
' them (abstract vs concrete; outward-facing vs inward-facing; who-we-are vs how-it-runs) and pick by',
|
|
85
|
+
' altitude and audience, not by keyword.',
|
|
86
|
+
'- Read one or two sibling notes in the chosen location and match their shape — frontmatter keys, heading',
|
|
87
|
+
' style, bullets vs prose — before writing.',
|
|
88
|
+
'',
|
|
89
|
+
'## 3. Sub-item under an existing location, or a new top-level one?',
|
|
90
|
+
'',
|
|
91
|
+
'Default to a sub-item until it earns promotion. A near-empty top-level location is its own kind of',
|
|
92
|
+
'clutter — worse than a slightly crowded existing one. Starting small and promoting later is cheap;',
|
|
93
|
+
'starting big and hollow is not. Make the promotion trigger a rule, not a vibe, and write it INTO the',
|
|
94
|
+
'note: "Promote to its own top-level location the moment this holds 3+ distinct notes or topics."',
|
|
95
|
+
'',
|
|
96
|
+
'## 4. Which visibility?',
|
|
97
|
+
'',
|
|
98
|
+
'Visibility is a placement input, not an afterthought, because the location sets the visibility default',
|
|
99
|
+
'(`ctxr note resolve <path>` shows the resolved value and where it came from):',
|
|
100
|
+
'',
|
|
101
|
+
'- Lean on the location\'s default; set the visibility field in frontmatter only when you must.',
|
|
102
|
+
'- Content that bridges two contexts goes to the more restrictive one — this store fails closed to its',
|
|
103
|
+
' default context (AGENTS.md names it) whenever nothing more specific applies.',
|
|
104
|
+
'- Visibility can override location: when the visibility the content requires differs from every location',
|
|
105
|
+
' that fits topically, the topical fit loses.',
|
|
106
|
+
'- Credentials, full account numbers, and secrets never enter the store — last-4 only.',
|
|
107
|
+
'',
|
|
108
|
+
'## The collision test — "should A and B be one location?"',
|
|
109
|
+
'',
|
|
110
|
+
'Before merging two locations, compare their visibility defaults (the placement section shows them). If',
|
|
111
|
+
'they differ, do NOT merge: a merged location has no safe default, every note in it needs a manual',
|
|
112
|
+
'override, and the day one is forgotten the fail-closed protection is gone — a structural guard traded for',
|
|
113
|
+
'human vigilance. If the two genuinely form one continuum with a fuzzy seam, place the boundary',
|
|
114
|
+
'deliberately and give the overlap ONE bridge note with an explicit visibility value, not a merged drawer.',
|
|
115
|
+
'A merge request often mis-detects that one side is itself mis-placed; the clean answer can be the',
|
|
116
|
+
'opposite of merging — split the mis-placed piece out and re-home it. Say so when you see it.',
|
|
117
|
+
'',
|
|
118
|
+
'## Perishable vs durable',
|
|
119
|
+
'',
|
|
120
|
+
'One capture often mixes a durable fact with perishable specifics. The durable part (the reusable',
|
|
121
|
+
'structure, mechanic, or trap) goes into the permanent note. The perishable part (this period\'s values,',
|
|
122
|
+
'links, boilerplate) goes into a fenced `contexture:<region>` block you OVERWRITE on each refresh, never',
|
|
123
|
+
'an accumulating pile of dead entries. Do not ingest a perishable item as source material — that',
|
|
124
|
+
'manufactures an artifact for the graveyard; capture the durable slice by hand instead.',
|
|
125
|
+
'',
|
|
126
|
+
'## Wire it in',
|
|
127
|
+
'',
|
|
128
|
+
'Add a one-line wikilink from the relevant hub note so the new note is not an orphan; `ctxr lint` flags',
|
|
129
|
+
'orphans and notes without a catalog entry.',
|
|
130
|
+
'',
|
|
131
|
+
'## Relocating an existing note',
|
|
132
|
+
'',
|
|
133
|
+
'`ctxr archive <path>` to retire it; a plain tracked `git mv` for an ordinary re-placement. Either way the',
|
|
134
|
+
'frontmatter — including the visibility field — is left untouched.',
|
|
135
|
+
'',
|
|
136
|
+
'## Verify',
|
|
137
|
+
'',
|
|
138
|
+
'`ctxr note resolve <path>` for the visibility; `ctxr lint` for orphans and catalog gaps; `ctxr doctor` for',
|
|
139
|
+
'anything that blocks a submit.',
|
|
140
|
+
],
|
|
141
|
+
};
|
|
142
|
+
const INGEST_ORCHESTRATION = {
|
|
143
|
+
file: 'ctxr-ingest-orchestration',
|
|
144
|
+
name: 'Ingest orchestration',
|
|
145
|
+
description: 'Capture raw material into the inbox, run the dedupe check, read the existing cluster, decide new/update/merge/restructure, and ingest with source identity via the contexture CLI.',
|
|
146
|
+
body: () => [
|
|
147
|
+
'Ingest is synthesis, not filing. The question is what the store should know after this source, not where',
|
|
148
|
+
'to put the file — "create a new note" is one option among several, never the default.',
|
|
149
|
+
'',
|
|
150
|
+
'1. Capture: write a plain markdown file directly into the inbox (see AGENTS.md\'s capture section) — no',
|
|
151
|
+
' provenance frontmatter; contexture assigns it at ingest.',
|
|
152
|
+
'2. Check: run `ctxr source check <path> --source-id <id>` and read the verdict — `new`, `already_ingested`,',
|
|
153
|
+
' `drift` (same identity, the source\'s content moved — read what changed and decide whether to update the',
|
|
154
|
+
' note, then `ctxr source stamp <path> --id <id>` once resolved), `alternate_source_match`, or',
|
|
155
|
+
' `multiple_matches`. On `multiple_matches`, stop and resolve the ambiguity yourself; never guess which',
|
|
156
|
+
' existing note it is. A legacy note with an identity but no hash on file: `ctxr source stamp` backfills',
|
|
157
|
+
' it. A source re-published under a new URL you\'ve confirmed is the same material:',
|
|
158
|
+
' `ctxr source add-alt <path> --id <new-id>` on the existing note, not a second ingest.',
|
|
159
|
+
'3. Read the source fully. Then read the existing cluster BEFORE writing anything: the catalog section for',
|
|
160
|
+
' the domain (`ctxr catalog show --section <id>`), every related note in it (all of them, not one or two),',
|
|
161
|
+
' and the graph (`ctxr graph build`, then read the graph document it writes at',
|
|
162
|
+
` \`${GRAPH_DOCUMENT_RELATIVE_PATH}\` for hub notes by cluster and cross-cluster bridges; \`ctxr graph query hubs\``,
|
|
163
|
+
' and `ctxr graph query neighbors <path>` on the closest note for the detail). Ask: what does the',
|
|
164
|
+
' store already know, and does this source confirm, extend, contradict, or nuance it?',
|
|
165
|
+
'4. Decide — the decision table:',
|
|
166
|
+
'',
|
|
167
|
+
' | The source… | Action |',
|
|
168
|
+
' |---|---|',
|
|
169
|
+
' | adds a genuinely new concept | create a new note |',
|
|
170
|
+
' | deepens an existing note | update and expand that note |',
|
|
171
|
+
' | makes two notes redundant | merge them; fix every cross-link |',
|
|
172
|
+
' | reveals a note is badly structured | recreate it with better structure |',
|
|
173
|
+
' | belongs as a section, not a page | add a section to the existing note |',
|
|
174
|
+
' | does several of the above | do all of them |',
|
|
175
|
+
'',
|
|
176
|
+
' - Hub check: if the cluster\'s hub already covers this, update the hub. If this source could become a',
|
|
177
|
+
' hub — a note many future notes will reference — create it at hub level, not as a leaf.',
|
|
178
|
+
' - Bridge check: if the source connects two clusters that have no bridge note, create one or add',
|
|
179
|
+
' explicit cross-links.',
|
|
180
|
+
' - Thesis-change rule: when new material contradicts a note, patch its top-level conclusion FIRST, then',
|
|
181
|
+
' hunt down the now-stale verdict language further down. A corrected body under an uncorrected',
|
|
182
|
+
' headline is worse than either alone.',
|
|
183
|
+
' - Source discipline: a field the source does not confirm is "not reported" — never inferred from',
|
|
184
|
+
' silence in either direction.',
|
|
185
|
+
'5. Write: for a new note, `ctxr ingest <path> --source-type <type> --source-id <id>` (stamps provenance and',
|
|
186
|
+
' gives it a catalog entry). Updates to existing notes are ordinary edits that preserve prior content',
|
|
187
|
+
' (a deliberate ingest despite an `alternate_source_match` is the same command).',
|
|
188
|
+
'6. Verify: `ctxr lint` (orphans, broken links, leftover inbox material) and `ctxr catalog check`.',
|
|
189
|
+
],
|
|
190
|
+
};
|
|
191
|
+
const CONNECTION_FINDING = {
|
|
192
|
+
file: 'ctxr-connection-finding',
|
|
193
|
+
name: 'Connection finding',
|
|
194
|
+
description: 'Traverse the wikilink graph of the store (neighbors, paths, hubs, orphans) to find what a note already connects to.',
|
|
195
|
+
body: () => [
|
|
196
|
+
'Traversal of links that already exist. To discover links a note SHOULD have, use `ctxr-connection-proposal`;',
|
|
197
|
+
'to synthesize an entity\'s current state from its sources, use `ctxr-rollup`.',
|
|
198
|
+
'',
|
|
199
|
+
'1. Run `ctxr graph build` to refresh the wikilink graph from the store\'s current notes, then read the graph',
|
|
200
|
+
` document it writes at \`${GRAPH_DOCUMENT_RELATIVE_PATH}\` — hub notes by cluster, cross-cluster bridges,`,
|
|
201
|
+
' orphans — for the cluster context the point queries below do not summarize.',
|
|
202
|
+
'2. To find what a note connects to or from, run `ctxr graph query neighbors <path>` (add `--depth` for',
|
|
203
|
+
' further hops, `--direction in|out|both`, `--type <relation>` to follow one configured relation,',
|
|
204
|
+
' `--as <context>` to see only what that context can see).',
|
|
205
|
+
'3. To find a path between two notes, run `ctxr graph query path <from> <to>`.',
|
|
206
|
+
'4. To find the most-referenced notes, run `ctxr graph query hubs`; to find unlinked ones,',
|
|
207
|
+
' `ctxr graph query orphans`; `ctxr graph query clusters` and `ctxr graph query bridges` show the',
|
|
208
|
+
' cluster structure and the notes that span it.',
|
|
209
|
+
'5. Report what the graph says — it enumerates structure and ranks nothing; judgment about which links',
|
|
210
|
+
' matter is yours.',
|
|
211
|
+
],
|
|
212
|
+
};
|
|
213
|
+
/**
|
|
214
|
+
* graph-context-document spec: the proposal skill groups by the CONFIGURED
|
|
215
|
+
* relation vocabulary (the same names `ctxr graph build` types edges from)
|
|
216
|
+
* and falls back to one group — never a relation name of its own.
|
|
217
|
+
*/
|
|
218
|
+
function relationGroupingStep(relations) {
|
|
219
|
+
if (relations.length === 0) {
|
|
220
|
+
return [
|
|
221
|
+
'5. This store configures no relation vocabulary (`retrieval.relations` is empty), so present proposals as',
|
|
222
|
+
' a single **Related** group. Format each item as `[[Note]]` — reason.',
|
|
223
|
+
];
|
|
224
|
+
}
|
|
225
|
+
return [
|
|
226
|
+
`5. Group proposals by this store's configured relation vocabulary: ${relations.map((r) => `**${r}**`).join(', ')}`,
|
|
227
|
+
' (`retrieval.relations` — the section headings carrying these names are what `ctxr graph build` types',
|
|
228
|
+
' edges from, so a link written under the right heading becomes a typed edge on the next build). Format',
|
|
229
|
+
' each item as `[[Note]]` — reason.',
|
|
230
|
+
];
|
|
231
|
+
}
|
|
232
|
+
const CONNECTION_PROPOSAL = {
|
|
233
|
+
file: 'ctxr-connection-proposal',
|
|
234
|
+
name: 'Connection proposal',
|
|
235
|
+
description: 'Discover the links a note should have, read each candidate before proposing, group by the store relation vocabulary, and write only approved links.',
|
|
236
|
+
body: (config) => [
|
|
237
|
+
'Link DISCOVERY — complementing `ctxr-connection-finding`, which traverses links that already exist.',
|
|
238
|
+
'',
|
|
239
|
+
'1. Read the target note in full. If given a name instead of a path, find the file; several candidates →',
|
|
240
|
+
' ask which one, never pick.',
|
|
241
|
+
'2. Extract its key concepts, entities (people, organizations, products), claims, and tags.',
|
|
242
|
+
'3. Search the store with those terms: content search (grep) for the phrases, the catalog section for the',
|
|
243
|
+
' domain (`ctxr catalog show --section <id>`), and `ctxr graph query neighbors <path> --depth 2` for',
|
|
244
|
+
' two-hop candidates the note does not link directly.',
|
|
245
|
+
'4. Read every candidate before proposing it. A keyword match is not a connection; each proposal states in',
|
|
246
|
+
' one line why the link is meaningful to a reader of the target note.',
|
|
247
|
+
...relationGroupingStep(config.retrieval.relations),
|
|
248
|
+
'6. Confirm before writing: present the grouped proposals and wait for approval by item. Do not write on',
|
|
249
|
+
' silence or on plan-level agreement.',
|
|
250
|
+
'7. Write approved links into the matching section of the target note, creating the relation sections the',
|
|
251
|
+
' sibling notes use when the note lacks them. No other edits to the note in the same pass.',
|
|
252
|
+
'8. Report: notes scanned, proposals per group, links written, and nearby orphans',
|
|
253
|
+
' (`ctxr graph query orphans`) that would also benefit from linking.',
|
|
254
|
+
],
|
|
255
|
+
};
|
|
256
|
+
const ROLLUP = {
|
|
257
|
+
file: 'ctxr-rollup',
|
|
258
|
+
name: 'Rollup',
|
|
259
|
+
description: 'Regenerate the synthesized current-state region of an entity note from every source that references it, with provenance for every fact.',
|
|
260
|
+
body: () => [
|
|
261
|
+
'Regenerate the machine-synthesized current-state region of an entity note (a person, organization,',
|
|
262
|
+
'product, initiative, or topic hub) from every note that references it. Hand-written content outside the',
|
|
263
|
+
'fenced region is never touched.',
|
|
264
|
+
'',
|
|
265
|
+
'Run `ctxr rollup stale` (or `--for <entity>` for one) to find entities worth re-rolling — it lists every',
|
|
266
|
+
'entity note whose backlinks have moved since its last synthesis, or which has never been rolled up at',
|
|
267
|
+
'all; use it instead of guessing from memory which entities are out of date.',
|
|
268
|
+
'',
|
|
269
|
+
'1. Resolve, never create. `<entity>` is a path or a note name; several candidates → list them and ask.',
|
|
270
|
+
' When a location has more than one plausible hub, prefer the shorter-named, undated one. If no entity',
|
|
271
|
+
' note exists, STOP and say so — creating it is a separate task, not a side effect of a rollup.',
|
|
272
|
+
'2. Refuse non-entities. Dated notes, journal entries, meeting notes, generated files, and store',
|
|
273
|
+
' infrastructure are INPUTS to rollups, not entities. If the name resolves to one of those, stop and ask',
|
|
274
|
+
' which entity was meant.',
|
|
275
|
+
'3. Gather: `ctxr rollup gather <entity>` enumerates the candidate sources (notes linking to it). Fewer',
|
|
276
|
+
' than 3 accepted sources → push back ("only N notes reference this; a rollup would be thin — continue?")',
|
|
277
|
+
' and do not auto-continue.',
|
|
278
|
+
'4. Read EVERY accepted source, not a sample — the value of a rollup is total recall over the entity\'s',
|
|
279
|
+
' corpus. Per source note its date (filename, frontmatter, then mtime as a last resort), its kind, and',
|
|
280
|
+
' what it says about THIS entity specifically. For a long source, grep for the entity first.',
|
|
281
|
+
'5. Synthesize into a file, in this shape — bullets throughout, short clauses; skip any empty subsection',
|
|
282
|
+
' silently (never "N/A", "none", or "TBD"):',
|
|
283
|
+
'',
|
|
284
|
+
' ```',
|
|
285
|
+
' ## Current state',
|
|
286
|
+
' ### Status — one fact per line; what it is, what is happening now, headline problem or metric',
|
|
287
|
+
' ### Recent activity — `YYYY-MM-DD — what happened [[Source]]`, most recent first',
|
|
288
|
+
' ### Open threads — unresolved decisions, blockers, pending actions; each links where it was raised',
|
|
289
|
+
' ### Key people — `[[Person]] — role in this entity\'s orbit`; only people present in the corpus',
|
|
290
|
+
' ### Sources — every note consulted, sorted; the audit trail',
|
|
291
|
+
' ```',
|
|
292
|
+
'',
|
|
293
|
+
' Provenance rules: every fact traceable to a source note — nothing from memory or earlier conversation;',
|
|
294
|
+
' no editorializing or recommendations; nothing dated later than today; inline `[[wikilinks]]` when one',
|
|
295
|
+
' claim has one clear source.',
|
|
296
|
+
'6. Write: `ctxr rollup write <entity> --content-file <file>` — an idempotent fenced write (`changed: false`',
|
|
297
|
+
' and a byte-identical file when the content matches; mismatched markers abort with nothing written).',
|
|
298
|
+
'7. Report: entity path, sources accepted, `changed` or unchanged, and notable findings (e.g. the oldest',
|
|
299
|
+
' unresolved thread). Anything outside the fence you want to fix is a separate, explicit edit.',
|
|
300
|
+
],
|
|
301
|
+
};
|
|
302
|
+
const SUBMIT = {
|
|
303
|
+
file: 'ctxr-submit',
|
|
304
|
+
name: 'Submit',
|
|
305
|
+
description: 'End a working session — re-scan, capture once, stage surgically, gate the external side effect, and open the reviewed pull request.',
|
|
306
|
+
body: (config) => {
|
|
307
|
+
const defaultBranch = config.git.default_branch;
|
|
308
|
+
return [
|
|
309
|
+
'The submit half of the session lifecycle — everything up to and including opening the pull request.',
|
|
310
|
+
'`ctxr-land` is the other half, after review; `ctxr-session-lifecycle` covers what surrounds both (starting',
|
|
311
|
+
'a session, the conflict playbook, sequencing several pull requests) and is not repeated here.',
|
|
312
|
+
'',
|
|
313
|
+
'1. Re-scan (mandatory — never replay a plan from an earlier snapshot): `git fetch origin`,',
|
|
314
|
+
' `git status --short`, `git diff --stat`, `git diff --cached --stat`,',
|
|
315
|
+
` \`git ls-files --others --exclude-standard\`, \`git log --oneline origin/${defaultBranch}..HEAD\`. State moves`,
|
|
316
|
+
' under you while you work; name the delta from the previous scan rather than silently folding new work',
|
|
317
|
+
' into old buckets.',
|
|
318
|
+
'2. Capture pass: run `ctxr-session-capture` exactly once here — closing a session is itself a capture',
|
|
319
|
+
' trigger; do not fire it again after submitting.',
|
|
320
|
+
'3. Stage surgically: `git add <paths>`, never `git add -A`; confirm with `git status --short` that the',
|
|
321
|
+
' staged set matches the intended unit. Derived artifacts under the cache paths never stage.',
|
|
322
|
+
'4. One coherent unit per pull request. If the session produced two disjoint units, say so and ask',
|
|
323
|
+
' whether to split.',
|
|
324
|
+
'5. Fire gate: `ctxr session submit` commits, pushes, and opens the pull request — an external side',
|
|
325
|
+
' effect, and plan consent is not fire consent. Present the branch, the title, and what rides it; wait',
|
|
326
|
+
' for an explicit go before running it.',
|
|
327
|
+
'6. Run: `ctxr session submit --branch "<name>" --title "<title>" --body "<why / what changed /',
|
|
328
|
+
' verification / follow-ups>"`. Name a real branch with `--branch` — never let a generated name reach',
|
|
329
|
+
' the forge. It runs the full store validation first; fix a failure, never bypass it. If no forge is',
|
|
330
|
+
' reachable it still pushes and prints the manual pull-request instructions.',
|
|
331
|
+
'7. Verify before any retry: a transport error can arrive AFTER the push or the pull-request open already',
|
|
332
|
+
' succeeded. Before retrying anything, `git ls-remote origin <branch>` and the forge\'s pull-request list',
|
|
333
|
+
' for the branch — never replay a push or a pull-request open blindly.',
|
|
334
|
+
'8. Hand off: report the pull request, then point at `ctxr-land` for after review.',
|
|
335
|
+
];
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
const LAND = {
|
|
339
|
+
file: 'ctxr-land',
|
|
340
|
+
name: 'Land',
|
|
341
|
+
description: 'Complete a reviewed session — merge its pull request, sync the default branch, and reclaim the worktree — one gated command, never a manual merge.',
|
|
342
|
+
body: (config) => {
|
|
343
|
+
const defaultBranch = config.git.default_branch;
|
|
344
|
+
return [
|
|
345
|
+
'The land half of the session lifecycle, after review. `ctxr-submit` is the other half; `ctxr-session-lifecycle`',
|
|
346
|
+
'covers what surrounds both and is not repeated here.',
|
|
347
|
+
'',
|
|
348
|
+
'1. Resolve the target: the current session branch by default, or name one with `--branch <name>` or',
|
|
349
|
+
` \`--pr <n>\`. Never run this against \`${defaultBranch}\` itself.`,
|
|
350
|
+
'2. Run `ctxr session land` (add `--yes` only when you already have explicit approval from elsewhere in',
|
|
351
|
+
' this conversation; otherwise let it prompt). It reads the pull request\'s state, gates the merge behind',
|
|
352
|
+
' an explicit confirmation, merges with `--merge-method` (default squash), confirms the forge reports',
|
|
353
|
+
' merged, and fast-forwards the default branch in the checkout it runs from when that checkout is on',
|
|
354
|
+
` \`${defaultBranch}\` — a checkout that cannot fast-forward is reported, never forced.`,
|
|
355
|
+
'3. Conflicting or unknown mergeability stops the command; follow `ctxr-session-lifecycle`\'s conflict',
|
|
356
|
+
' playbook, then retry — a retry re-reads state and performs only what remains.',
|
|
357
|
+
'4. Add `--reap` to remove the session worktree in the same run, once it is clean and merged; otherwise',
|
|
358
|
+
' run `ctxr session reap` afterward, or leave cleanup to whoever owns the worktree.',
|
|
359
|
+
'5. Report exactly what the command reported — merged or not, synced or not (with why not), reaped or not',
|
|
360
|
+
' (with why not). Never claim a merge or a cleanup the command itself did not confirm.',
|
|
361
|
+
'',
|
|
362
|
+
'Never merge by hand (a raw forge-CLI merge, the forge\'s web UI on this agent\'s behalf): `ctxr session land`',
|
|
363
|
+
'is the only merge path, so every landing leaves the same audit trail and passes through the same gate.',
|
|
364
|
+
];
|
|
26
365
|
},
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
366
|
+
};
|
|
367
|
+
const SESSION_LIFECYCLE = {
|
|
368
|
+
file: 'ctxr-session-lifecycle',
|
|
369
|
+
name: 'Session lifecycle',
|
|
370
|
+
description: 'Start a session worktree, re-scan before any plan, resolve conflicts, and sequence multiple pull requests — the frame ctxr-submit and ctxr-land sit inside.',
|
|
371
|
+
body: (config) => {
|
|
372
|
+
const defaultBranch = config.git.default_branch;
|
|
373
|
+
return [
|
|
374
|
+
'Every write lands via a session worktree and a reviewed pull request; nothing commits to the default',
|
|
375
|
+
`branch (\`${defaultBranch}\`). This skill covers what surrounds a session — starting one, re-scanning before`,
|
|
376
|
+
'any plan, resolving conflicts, and sequencing several pull requests. `ctxr-submit` and `ctxr-land` are',
|
|
377
|
+
'the two verbs at the seams; their steps are not repeated here.',
|
|
378
|
+
'',
|
|
379
|
+
'## Start',
|
|
380
|
+
'',
|
|
381
|
+
'`ctxr session start` creates a worktree on a fresh branch off the fetched default branch; work there.',
|
|
382
|
+
'If you find yourself on the default branch or in the root checkout, stop — `ctxr session list` shows the',
|
|
383
|
+
'active sessions to work in instead.',
|
|
384
|
+
'',
|
|
385
|
+
'## Re-scan before any plan',
|
|
386
|
+
'',
|
|
387
|
+
'Re-scan whenever state may have moved under you — before presenting a plan, before running `ctxr-submit`,',
|
|
388
|
+
'after any gap in the conversation: `git fetch origin`, `git status --short`, `git diff --stat`,',
|
|
389
|
+
'`git diff --cached --stat`, `git ls-files --others --exclude-standard`,',
|
|
390
|
+
`\`git log --oneline origin/${defaultBranch}..HEAD\`. State moves under you while you work; a plan that grows`,
|
|
391
|
+
'between scans is normal. Name the delta from the previous scan rather than silently folding new work into',
|
|
392
|
+
'old buckets.',
|
|
393
|
+
'',
|
|
394
|
+
'## Conflict playbook',
|
|
395
|
+
'',
|
|
396
|
+
`From the session worktree: \`git fetch origin && git rebase origin/${defaultBranch}\`. For each conflicting`,
|
|
397
|
+
'file inspect both sides (`git show`), understand what each represents, and produce a version that keeps',
|
|
398
|
+
'both meanings — never take one side blindly. `git add <file>`, `GIT_EDITOR=true git rebase --continue`,',
|
|
399
|
+
'then `git push --force-with-lease origin <branch>` (aborts if the remote moved underneath you). Never',
|
|
400
|
+
'resolve on the default branch.',
|
|
401
|
+
'',
|
|
402
|
+
'## Multi-PR sequencing',
|
|
403
|
+
'',
|
|
404
|
+
'Parallel when the units are file-disjoint — one `ctxr session start` per unit. Sequential when files',
|
|
405
|
+
'overlap: wait for the merge (`ctxr-land`), then start the next session off the updated default branch.',
|
|
406
|
+
'Worktrees isolate directories, not logical writes: a hot file every session appends to collides at merge',
|
|
407
|
+
'time regardless — sequence those.',
|
|
408
|
+
'',
|
|
409
|
+
'## Reclaiming',
|
|
410
|
+
'',
|
|
411
|
+
'`ctxr session reap` removes merged, clean worktrees (or use `ctxr-land`\'s `--reap`); `ctxr session abandon',
|
|
412
|
+
'<branch>` discards work and needs an explicit go. Never claim cleanup happened without having run one.',
|
|
413
|
+
];
|
|
45
414
|
},
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
415
|
+
};
|
|
416
|
+
const SESSION_CAPTURE = {
|
|
417
|
+
file: 'ctxr-session-capture',
|
|
418
|
+
name: 'Session capture',
|
|
419
|
+
description: 'At the end of a session, propose durable store notes, world facts, and user facts in one message with per-item approval, then write only what was approved.',
|
|
420
|
+
body: (config) => {
|
|
421
|
+
const worldFactsPath = identityFilePath(config, 'world-facts');
|
|
422
|
+
const userFactsPath = identityFilePath(config, 'user-facts');
|
|
423
|
+
return [
|
|
424
|
+
'End-of-session capture: propose what the session produced that is durable, write only what is approved.',
|
|
425
|
+
'The approval gate is what keeps the blast radius low — nothing is written without it.',
|
|
426
|
+
'',
|
|
427
|
+
'## When',
|
|
428
|
+
'',
|
|
429
|
+
'Fires on the user\'s own closing utterance ("done", "ship it", "that\'s all", a sign-off) or an explicit',
|
|
430
|
+
'request. A request to open a pull request is also a wrap signal — finish the pull request first, then',
|
|
431
|
+
'propose (after the LAST one of a batch, not once per request).',
|
|
432
|
+
'',
|
|
433
|
+
'Anti-triggers — stay silent when: an error, failed test, pending question, or requested follow-up is',
|
|
434
|
+
'outstanding (the session is not over); the recent turns are dominated by tuning the store or this skill',
|
|
435
|
+
'itself (nothing is capturable until that lands and gets used); the items would substantially repeat a',
|
|
436
|
+
'proposal already made this session. The agent\'s own summary never counts as a signal, nor does a cue',
|
|
437
|
+
'word inside quoted text, nor a request for a session summary. Zero durable items → silent; never emit',
|
|
438
|
+
'"scanned, nothing to save".',
|
|
439
|
+
'',
|
|
440
|
+
'## What is durable',
|
|
441
|
+
'',
|
|
442
|
+
'Include: decisions with their rationale; net-new concepts; net-new people, organizations, or',
|
|
443
|
+
'initiatives with context; reusable patterns and playbooks; resolved non-obvious problems worth future',
|
|
444
|
+
'recall; durable preference or environment facts the user corrected you on.',
|
|
445
|
+
'',
|
|
446
|
+
'Exclude: tool output, command traces, transient debugging; anything already in the identity files;',
|
|
447
|
+
'read-only investigation with no conclusion; scratch work that landed on no decision.',
|
|
448
|
+
'',
|
|
449
|
+
'Unsure → propose (the user can decline) — but when in doubt between a note and identity, pick the note:',
|
|
450
|
+
'notes are retrieval-gated, identity is loaded into every session, so its blast radius is higher.',
|
|
451
|
+
'',
|
|
452
|
+
'## Proposal — ONE message, up to three blocks, empty blocks omitted',
|
|
453
|
+
'',
|
|
454
|
+
'```',
|
|
455
|
+
'### Block A — store notes',
|
|
456
|
+
'- A1 path: <layer>/<location>/<Title>.md',
|
|
457
|
+
' visibility: <value> — <one-line rationale; `ctxr note resolve` on a sibling shows the default>',
|
|
458
|
+
' sketch: bullets',
|
|
459
|
+
`### Block B — world facts (${worldFactsPath})`,
|
|
460
|
+
'- B1 action: add | replace | remove (replace/remove name one unique existing line)',
|
|
461
|
+
' content: "..."',
|
|
462
|
+
`### Block C — user facts (${userFactsPath})`,
|
|
463
|
+
'- C1 action: add',
|
|
464
|
+
' content: "..."',
|
|
465
|
+
'```',
|
|
466
|
+
'',
|
|
467
|
+
'Secret-marker pass: any item whose content looks like a credential gets `⚠ suspected-secret:` on its',
|
|
468
|
+
'own line with why (patterns worth checking: `sk-`, `ghp_`, `AKIA` + 16 chars, `Bearer …`, a JWT',
|
|
469
|
+
'`eyJ…`, a URL with a token/key/secret parameter). The marker is a hint; the user is the gate.',
|
|
470
|
+
'',
|
|
471
|
+
'End with: "Approve by ID (e.g. `A1 A3 B2`) or `skip`." An edit request → re-propose. An ambiguous',
|
|
472
|
+
'answer → do not proceed.',
|
|
473
|
+
'',
|
|
474
|
+
'## Apply',
|
|
475
|
+
'',
|
|
476
|
+
'Write ONLY the approved items to a YAML proposal file, in the shape `ctxr session capture` reads:',
|
|
477
|
+
'',
|
|
478
|
+
'```yaml',
|
|
479
|
+
'notes:',
|
|
480
|
+
' - id: A1',
|
|
481
|
+
' path: <layer>/<location>/<Title>.md',
|
|
482
|
+
' mode: create # or: append',
|
|
483
|
+
' visibility: <value> # optional; omit to take the location default',
|
|
484
|
+
' body: |',
|
|
485
|
+
' matching the frontmatter and style of the sibling notes (`ctxr-placement` decides the location)',
|
|
486
|
+
'world_facts:',
|
|
487
|
+
' - id: B1',
|
|
488
|
+
' action: add # or: replace | remove',
|
|
489
|
+
' text: "declarative fact, never an imperative"',
|
|
490
|
+
' match: "..." # required for replace/remove: a unique substring of the existing entry',
|
|
491
|
+
'user_facts:',
|
|
492
|
+
' - id: C1',
|
|
493
|
+
' action: add',
|
|
494
|
+
' text: "who the user is, not what they did this session"',
|
|
495
|
+
'```',
|
|
496
|
+
'',
|
|
497
|
+
'Then run `ctxr session capture --proposal <file>`. It validates and writes every item independently —',
|
|
498
|
+
'one bad path never blocks the rest — creating or appending notes and applying identity deltas through',
|
|
499
|
+
'the entry primitive. These note commits ride the session pull request (`ctxr-session-lifecycle`). Never',
|
|
500
|
+
`edit \`${worldFactsPath}\` or \`${userFactsPath}\` directly, and never write identity through a`,
|
|
501
|
+
'harness-specific memory mechanism — the command is the only writer.',
|
|
502
|
+
'',
|
|
503
|
+
'## Report from actual writes',
|
|
504
|
+
'',
|
|
505
|
+
'Report exactly what the command reported — wrote / appended / refused (with reason) / skipped, by ID —',
|
|
506
|
+
'never from the proposal itself. A refused item is surfaced prominently with its reason.',
|
|
507
|
+
];
|
|
65
508
|
},
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
1.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
509
|
+
};
|
|
510
|
+
const DERIVED_ARTIFACTS = {
|
|
511
|
+
file: 'ctxr-derived-artifacts',
|
|
512
|
+
name: 'Derived artifacts',
|
|
513
|
+
description: 'Refresh a generated artifact safely — check before build, read the counts back, never hand-edit inside a fence, keep derived files out of content commits.',
|
|
514
|
+
body: (config) => {
|
|
515
|
+
const defaultBranch = config.git.default_branch;
|
|
516
|
+
return [
|
|
517
|
+
'A derived artifact is the deterministic output of a build over the store: catalog sections, the graph,',
|
|
518
|
+
'the generated AGENTS.md sections, the entry files, the owned skills, adapter outputs. They rot two ways —',
|
|
519
|
+
'stale (the source moved, the artifact did not) and clobbered (a build against the wrong base, or a hand',
|
|
520
|
+
'edit inside a fence that the next build erases).',
|
|
521
|
+
'',
|
|
522
|
+
'1. Identify the source of truth and the builder: catalog ← notes (`ctxr catalog build`); graph ← notes',
|
|
523
|
+
' (`ctxr graph build`, lives under the cache paths, never committed); AGENTS.md sections, entry files,',
|
|
524
|
+
' skills, hooks ← config + the installed package (`ctxr update`); adapter outputs ← config',
|
|
525
|
+
' (`ctxr adapters generate`).',
|
|
526
|
+
'2. Check BEFORE you build: `ctxr catalog check` (add `--stale` for glosses whose note changed) and',
|
|
527
|
+
' `ctxr doctor`. Zero coverage, a parse error, or a count far below the store\'s real note count means',
|
|
528
|
+
' the builder and the base disagree — STOP; a build now writes a broken or empty artifact. Fix the base,',
|
|
529
|
+
' or hand-add only your entries, matching the committed structure, and say so in the commit message.',
|
|
530
|
+
'3. Build, then read the result back: reported counts against the source (notes vs catalog entries, graph',
|
|
531
|
+
' nodes vs notes); your new notes present with non-zero links; nothing else lost.',
|
|
532
|
+
'4. Fences: never hand-edit inside a `contexture:<region>` fence — the next build overwrites it. Hand',
|
|
533
|
+
' edits OUTSIDE a fence (catalog glosses, prose around a generated section) are preserved by every',
|
|
534
|
+
' build and are the right place for them. A note\'s own operator-defined fenced region (a ledger, a',
|
|
535
|
+
' log, any block maintained via `ctxr entry append <note> --region <name>`) is a different concern —',
|
|
536
|
+
' that append is the sanctioned write into it, never a hand edit.',
|
|
537
|
+
'5. Commits: artifacts under the cache paths never stage. Committed derived files (the catalog) ride',
|
|
538
|
+
' their own small change AFTER the content lands, path-scoped (`git add <path>`), never swept up with',
|
|
539
|
+
' `-A` in a checkout other sessions use.',
|
|
540
|
+
`6. Verify the remote, not the claim: after "it's merged", \`git fetch origin\` and`,
|
|
541
|
+
` \`git show origin/${defaultBranch}:<path> | grep -c <marker>\` before treating the loop as closed.`,
|
|
542
|
+
];
|
|
83
543
|
},
|
|
544
|
+
};
|
|
545
|
+
const ORGANIZE_AUDIT = {
|
|
546
|
+
file: 'ctxr-organize-audit',
|
|
547
|
+
name: 'Organize audit',
|
|
548
|
+
description: 'Audit store health with ctxr lint (observations) and ctxr doctor (blocking invariants), retire by moving, and classify broken links before fixing them.',
|
|
549
|
+
body: () => [
|
|
550
|
+
'1. Run `ctxr lint` for the full health report — orphan notes, broken links, uningested inbox material,',
|
|
551
|
+
' catalog gaps. It always exits 0; its findings are observations for judgment, never a block.',
|
|
552
|
+
'2. Run `ctxr doctor` for the invariants that DO block: catalog coverage, fail-closed visibility, hook',
|
|
553
|
+
' health, and more. Address doctor\'s failures before `ctxr session submit`; it runs the same checks.',
|
|
554
|
+
'',
|
|
555
|
+
'## Placement review',
|
|
556
|
+
'',
|
|
557
|
+
'The diagnostic that works: "what standard am I maintaining here, and what is my review cadence?" A',
|
|
558
|
+
'location reviewed on a cadence is an ongoing responsibility; one touched only when its material is needed',
|
|
559
|
+
'is reference — whatever its label says. Apply a file-count sanity check first: dozens of reference',
|
|
560
|
+
'readings under a responsibility label is drift. Watch for topic libraries creeping back into layers',
|
|
561
|
+
'organized by actionability. Single-note calls go through `ctxr-placement`.',
|
|
562
|
+
'',
|
|
563
|
+
'## Retiring: move, don\'t tag',
|
|
564
|
+
'',
|
|
565
|
+
'A status tag left in place defeats the layer — the active layers must show only active work.',
|
|
566
|
+
'`ctxr archive <path>` moves the note into the configured archive location as a tracked rename; the',
|
|
567
|
+
'visibility field travels unchanged (never rewrite it on the move). Retiring is reversible cold storage,',
|
|
568
|
+
'not deletion. Verify with `git status --short` showing `R` (a rename, history preserved), not a delete',
|
|
569
|
+
'plus an add, then `ctxr catalog check`.',
|
|
570
|
+
'',
|
|
571
|
+
'## Stale rollups',
|
|
572
|
+
'',
|
|
573
|
+
'`ctxr lint`\'s rollup-stale finding (and `ctxr rollup stale` directly) names entity notes whose backlinks',
|
|
574
|
+
'have moved since their last synthesis, or which have never been rolled up — hand each one to',
|
|
575
|
+
'`ctxr-rollup` rather than re-synthesizing ad hoc.',
|
|
576
|
+
'',
|
|
577
|
+
'## Leak scan',
|
|
578
|
+
'',
|
|
579
|
+
'When a store declares context markers (`disclosure.leak_markers`), `ctxr lint` also flags content',
|
|
580
|
+
'belonging to one context found inside a note that context cannot see; `ctxr check <path> --scan` runs',
|
|
581
|
+
'the same scan on one note without needing an `--audience`. A finding names the marker context and the',
|
|
582
|
+
'matched text — read it, then move or redact the content; the scan never auto-fixes.',
|
|
583
|
+
'',
|
|
584
|
+
'## Broken links have classes — classify before fixing',
|
|
585
|
+
'',
|
|
586
|
+
'- URL wrapped in wikilink syntax → convert to a markdown link.',
|
|
587
|
+
'- Basename collision (differs from an existing note only by case, hyphenation, or display name) →',
|
|
588
|
+
' rewrite with `[[Real Name|display]]` alias syntax.',
|
|
589
|
+
'- Dangling: bugs (typos, accidental self-references, a path rewrite that hit a folder name) get fixed;',
|
|
590
|
+
' healthy forward references (a name you may write about later, a planned hub) are intent markers —',
|
|
591
|
+
' leave them. Never fabricate stub notes to silence them; a one-line stub hides the real TODO and makes',
|
|
592
|
+
' orphan analysis lie.',
|
|
593
|
+
'',
|
|
594
|
+
'## Moves at scale',
|
|
595
|
+
'',
|
|
596
|
+
'Two commits, never a broken intermediate: commit 1 = the moves PLUS the wikilink fixes they require;',
|
|
597
|
+
'commit 2 = tooling and doc patches. Before the moves, grep for hardcoded references to the old paths.',
|
|
598
|
+
'After they land, rebuild the derived artifacts (`ctxr-derived-artifacts`) and re-run `ctxr lint`.',
|
|
599
|
+
],
|
|
600
|
+
};
|
|
601
|
+
export const PROCEDURES = [
|
|
602
|
+
INGEST_ORCHESTRATION,
|
|
603
|
+
PLACEMENT,
|
|
604
|
+
CONNECTION_FINDING,
|
|
605
|
+
CONNECTION_PROPOSAL,
|
|
606
|
+
ROLLUP,
|
|
607
|
+
SESSION_LIFECYCLE,
|
|
608
|
+
SUBMIT,
|
|
609
|
+
LAND,
|
|
610
|
+
SESSION_CAPTURE,
|
|
611
|
+
DERIVED_ARTIFACTS,
|
|
612
|
+
ORGANIZE_AUDIT,
|
|
84
613
|
];
|
|
614
|
+
/** The owned skills, rendered against one store's configuration — what `syncShippedSkills` writes. */
|
|
615
|
+
export function renderProcedures(config) {
|
|
616
|
+
return PROCEDURES.map((seed) => ({
|
|
617
|
+
file: seed.file,
|
|
618
|
+
name: seed.name,
|
|
619
|
+
description: seed.description,
|
|
620
|
+
content: skillDocument(seed, config),
|
|
621
|
+
}));
|
|
622
|
+
}
|
|
85
623
|
/**
|
|
86
624
|
* entry-doc-generation spec: every skill actually on disk — the contexture-
|
|
87
625
|
* owned ones plus any operator-authored ones. This is what the AGENTS.md
|
|
@@ -105,7 +643,7 @@ export function procedurePaths(config) {
|
|
|
105
643
|
*/
|
|
106
644
|
export async function syncShippedSkills(root, config) {
|
|
107
645
|
const changed = [];
|
|
108
|
-
for (const procedure of
|
|
646
|
+
for (const procedure of renderProcedures(config)) {
|
|
109
647
|
const relativePath = path
|
|
110
648
|
.join(config.harness.procedures_path, procedure.file, SKILL_FILE_NAME)
|
|
111
649
|
.split(path.sep)
|