skillwiki 0.10.22 → 0.10.23
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/chunk-6ZDKTNLA.js +294 -0
- package/dist/chunk-EQPU2BPM.js +468 -0
- package/dist/{chunk-54JVBNWK.js → chunk-QBZYEEBD.js} +12 -295
- package/dist/chunk-QNTBNNEL.js +616 -0
- package/dist/{chunk-7YDWC6D3.js → chunk-SYKSL3JQ.js} +852 -699
- package/dist/cli.js +613 -146
- package/dist/{index-projection-Q6WL76SO.js → index-projection-FRWTZB5Y.js} +2 -1
- package/dist/skillwiki-mcp.js +4 -2
- package/dist/sources-L2SQV63E.js +10 -0
- package/package.json +1 -1
- package/skills/.claude-plugin/plugin.json +1 -1
- package/skills/.codex-plugin/plugin.json +1 -1
- package/skills/README.md +13 -2
- package/skills/agents/wiki-add-task.md +2 -1
- package/skills/agents/wiki-archive.md +8 -9
- package/skills/agents/wiki-audit.md +2 -1
- package/skills/agents/wiki-lint.md +2 -1
- package/skills/agents/wiki-query.md +3 -1
- package/skills/agents/wiki-reingest.md +9 -8
- package/skills/package.json +1 -1
- package/skills/skills/using-skillwiki/SKILL.md +19 -13
- package/skills/skills/wiki-add-task/SKILL.md +4 -3
- package/skills/skills/wiki-archive/SKILL.md +23 -18
- package/skills/skills/wiki-audit/SKILL.md +3 -1
- package/skills/skills/wiki-init/SKILL.md +12 -1
- package/skills/skills/wiki-lint/SKILL.md +2 -1
- package/skills/skills/wiki-query/SKILL.md +7 -0
- package/skills/skills/wiki-reingest/SKILL.md +12 -8
- package/skills/skills/wiki-remove/SKILL.md +22 -5
- package/skills/skills/wiki-sync/SKILL.md +2 -3
- package/skills/using-skillwiki/SKILL.md +19 -13
- package/skills/wiki-add-task/SKILL.md +4 -3
- package/skills/wiki-archive/SKILL.md +23 -18
- package/skills/wiki-audit/SKILL.md +3 -1
- package/skills/wiki-init/SKILL.md +12 -1
- package/skills/wiki-lint/SKILL.md +2 -1
- package/skills/wiki-query/SKILL.md +7 -0
- package/skills/wiki-reingest/SKILL.md +12 -8
- package/skills/wiki-remove/SKILL.md +22 -5
- package/skills/wiki-sync/SKILL.md +2 -3
- package/templates/SCHEMA.md +8 -5
- package/templates/web-clipper/llm-wiki-clippings.json +56 -0
- package/templates/web-clipper/readme.txt +16 -0
package/dist/cli.js
CHANGED
|
@@ -9,16 +9,14 @@ import {
|
|
|
9
9
|
acquireLock,
|
|
10
10
|
acquireOwnedSyncLock,
|
|
11
11
|
appendLastOp,
|
|
12
|
+
applyRawStructuralMove,
|
|
12
13
|
assessSourceIdentity,
|
|
13
14
|
buildDegradedReasons,
|
|
14
15
|
buildRemoteObjectPath,
|
|
15
|
-
canonicalEventJson,
|
|
16
16
|
clearLastOp,
|
|
17
17
|
configPath,
|
|
18
18
|
evaluateDirtyVolumeGate,
|
|
19
19
|
evaluateSatelliteRunHealth,
|
|
20
|
-
eventPathFor,
|
|
21
|
-
extractCitationMarkers,
|
|
22
20
|
extractTaxonomy,
|
|
23
21
|
findPlugin,
|
|
24
22
|
fixPathTooLong,
|
|
@@ -30,11 +28,11 @@ import {
|
|
|
30
28
|
mergeTaxonomyConflict,
|
|
31
29
|
normalizeRemoteRoot,
|
|
32
30
|
planAndMaybePruneRemoteObjects,
|
|
31
|
+
planRawStructuralMove,
|
|
33
32
|
probeRemoteHealth,
|
|
34
33
|
readCliPackageJson,
|
|
35
34
|
readLastOp,
|
|
36
35
|
readLock,
|
|
37
|
-
readLogEvents,
|
|
38
36
|
readSatelliteLatestRunFromText,
|
|
39
37
|
reconcileTaxonomyDocument,
|
|
40
38
|
releaseLock,
|
|
@@ -43,6 +41,7 @@ import {
|
|
|
43
41
|
renderProjectIndex,
|
|
44
42
|
resolveInitTimePath,
|
|
45
43
|
resolveRuntimePath,
|
|
44
|
+
rewriteRawSourceReferences,
|
|
46
45
|
runAudit,
|
|
47
46
|
runConfigGet,
|
|
48
47
|
runConfigList,
|
|
@@ -78,12 +77,11 @@ import {
|
|
|
78
77
|
safeWritePage,
|
|
79
78
|
satelliteLatestRunPath,
|
|
80
79
|
scanConflictMarkerBlocksInText,
|
|
80
|
+
snapshotMaintainedPageState,
|
|
81
81
|
snapshotterHealthChecks,
|
|
82
82
|
taxonomyCommentForPage,
|
|
83
|
-
upsertIndexEntry
|
|
84
|
-
|
|
85
|
-
writeLogEvent
|
|
86
|
-
} from "./chunk-7YDWC6D3.js";
|
|
83
|
+
upsertIndexEntry
|
|
84
|
+
} from "./chunk-SYKSL3JQ.js";
|
|
87
85
|
import {
|
|
88
86
|
normalizeDistTag,
|
|
89
87
|
readCache,
|
|
@@ -96,16 +94,10 @@ import {
|
|
|
96
94
|
UNMANAGED_START,
|
|
97
95
|
assertTargetInsideVault,
|
|
98
96
|
atomicWriteText,
|
|
99
|
-
extractFrontmatter,
|
|
100
97
|
prepareTypedPage,
|
|
101
|
-
readPage,
|
|
102
|
-
redactSensitiveContent,
|
|
103
98
|
renderRootIndex,
|
|
104
|
-
scanSensitiveContent,
|
|
105
|
-
scanVault,
|
|
106
|
-
splitFrontmatter,
|
|
107
99
|
writeRootIndexProjection
|
|
108
|
-
} from "./chunk-
|
|
100
|
+
} from "./chunk-QBZYEEBD.js";
|
|
109
101
|
import {
|
|
110
102
|
runManagedWritePreflight,
|
|
111
103
|
runManagedWriteTransaction
|
|
@@ -138,6 +130,38 @@ import {
|
|
|
138
130
|
supersedeStaleReviewRequiredJournals,
|
|
139
131
|
writeDotenv
|
|
140
132
|
} from "./chunk-PQG26AGJ.js";
|
|
133
|
+
import {
|
|
134
|
+
applySourceDisposition,
|
|
135
|
+
decodeSourceActionApproval,
|
|
136
|
+
encodeSourceActionApproval,
|
|
137
|
+
inventorySources,
|
|
138
|
+
planSourceDisposition,
|
|
139
|
+
runSourcesPending
|
|
140
|
+
} from "./chunk-EQPU2BPM.js";
|
|
141
|
+
import {
|
|
142
|
+
authorizeRawOperation,
|
|
143
|
+
buildSourceReferenceIndex,
|
|
144
|
+
buildSourceRelocationProjection,
|
|
145
|
+
canonicalEventJson,
|
|
146
|
+
classifyRawPath,
|
|
147
|
+
eventPathFor,
|
|
148
|
+
extractCitationMarkers,
|
|
149
|
+
lifecycleDestination,
|
|
150
|
+
operationId,
|
|
151
|
+
readLogEvents,
|
|
152
|
+
readSourceRelocations,
|
|
153
|
+
resolveExistingRegularFileInsideVault,
|
|
154
|
+
validateLogEvent,
|
|
155
|
+
writeLogEvent
|
|
156
|
+
} from "./chunk-QNTBNNEL.js";
|
|
157
|
+
import {
|
|
158
|
+
extractFrontmatter,
|
|
159
|
+
readPage,
|
|
160
|
+
redactSensitiveContent,
|
|
161
|
+
scanSensitiveContent,
|
|
162
|
+
scanVault,
|
|
163
|
+
splitFrontmatter
|
|
164
|
+
} from "./chunk-6ZDKTNLA.js";
|
|
141
165
|
import {
|
|
142
166
|
ExitCode,
|
|
143
167
|
MetaSchema,
|
|
@@ -150,7 +174,7 @@ import {
|
|
|
150
174
|
} from "./chunk-C2DKFJFA.js";
|
|
151
175
|
|
|
152
176
|
// src/cli.ts
|
|
153
|
-
import { join as
|
|
177
|
+
import { join as join39 } from "path";
|
|
154
178
|
import { Command } from "commander";
|
|
155
179
|
|
|
156
180
|
// src/utils/output.ts
|
|
@@ -212,10 +236,10 @@ async function runHash(input) {
|
|
|
212
236
|
const split = splitFrontmatter(text);
|
|
213
237
|
if (!split.ok) return { exitCode: ExitCode.MISSING_CLOSING_DELIMITER, result: split };
|
|
214
238
|
const bodyBytes = Buffer.from(split.data.body, "utf8");
|
|
215
|
-
const
|
|
239
|
+
const sha2562 = createHash("sha256").update(bodyBytes).digest("hex");
|
|
216
240
|
return {
|
|
217
241
|
exitCode: ExitCode.OK,
|
|
218
|
-
result: ok({ path: input.file, sha256, byte_count: bodyBytes.byteLength, humanHint:
|
|
242
|
+
result: ok({ path: input.file, sha256: sha2562, byte_count: bodyBytes.byteLength, humanHint: sha2562 })
|
|
219
243
|
};
|
|
220
244
|
}
|
|
221
245
|
|
|
@@ -590,7 +614,7 @@ async function runLang(input) {
|
|
|
590
614
|
|
|
591
615
|
// src/commands/init.ts
|
|
592
616
|
import { mkdir as mkdir3, readFile as readFile3, readdir as readdir2, writeFile as writeFile2 } from "fs/promises";
|
|
593
|
-
import { join as join5 } from "path";
|
|
617
|
+
import { join as join5, dirname as dirname3 } from "path";
|
|
594
618
|
var DEFAULT_TAXONOMY = [
|
|
595
619
|
"research",
|
|
596
620
|
"comparison",
|
|
@@ -608,6 +632,12 @@ var VAULT_DIRS = [
|
|
|
608
632
|
"raw/papers",
|
|
609
633
|
"raw/transcripts",
|
|
610
634
|
"raw/assets",
|
|
635
|
+
"raw/archived/articles",
|
|
636
|
+
"raw/archived/papers",
|
|
637
|
+
"raw/archived/transcripts",
|
|
638
|
+
"raw/duplicates/articles",
|
|
639
|
+
"raw/duplicates/papers",
|
|
640
|
+
"raw/duplicates/transcripts",
|
|
611
641
|
"entities",
|
|
612
642
|
"concepts",
|
|
613
643
|
"comparisons",
|
|
@@ -620,6 +650,8 @@ var VAULT_DIRS = [
|
|
|
620
650
|
var ATTACHMENT_FOLDER = "raw/assets";
|
|
621
651
|
var NEW_FILE_FOLDER = "raw/transcripts";
|
|
622
652
|
var TEMPLATE_FOLDER = "_Templates";
|
|
653
|
+
var WEB_CLIPPER_TEMPLATE_REL = `${TEMPLATE_FOLDER}/web-clipper/llm-wiki-clippings.json`;
|
|
654
|
+
var WEB_CLIPPER_README_REL = `${TEMPLATE_FOLDER}/web-clipper/readme.txt`;
|
|
623
655
|
function extractDomainFromSchema(text) {
|
|
624
656
|
const m = text.match(/^##\s+Domain\s*\n([\s\S]*?)(?=\n\n|\n##|\s*$)/m);
|
|
625
657
|
if (!m) return "";
|
|
@@ -746,6 +778,23 @@ async function runInit(input) {
|
|
|
746
778
|
return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { file: fileName, message: String(e) }) };
|
|
747
779
|
}
|
|
748
780
|
}
|
|
781
|
+
async function copyTemplateOrPreserve(fileName, sourceName) {
|
|
782
|
+
const destination = join5(target, fileName);
|
|
783
|
+
try {
|
|
784
|
+
await readFile3(destination);
|
|
785
|
+
preserved.push(fileName);
|
|
786
|
+
return void 0;
|
|
787
|
+
} catch {
|
|
788
|
+
}
|
|
789
|
+
try {
|
|
790
|
+
await mkdir3(dirname3(destination), { recursive: true });
|
|
791
|
+
await writeFile2(destination, await readFile3(join5(input.templates, sourceName)));
|
|
792
|
+
created.push(fileName);
|
|
793
|
+
return void 0;
|
|
794
|
+
} catch (e) {
|
|
795
|
+
return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { file: fileName, message: String(e) }) };
|
|
796
|
+
}
|
|
797
|
+
}
|
|
749
798
|
const err1 = await writeOrPreserve("index.md", async () => {
|
|
750
799
|
const tpl = await readFile3(join5(input.templates, "index.md"), "utf8");
|
|
751
800
|
return tpl.replace("{{INIT_DATE}}", today);
|
|
@@ -773,6 +822,16 @@ async function runInit(input) {
|
|
|
773
822
|
].join("\n");
|
|
774
823
|
});
|
|
775
824
|
if (errTemplate) return errTemplate;
|
|
825
|
+
const errWebClipperTemplate = await copyTemplateOrPreserve(
|
|
826
|
+
WEB_CLIPPER_TEMPLATE_REL,
|
|
827
|
+
"web-clipper/llm-wiki-clippings.json"
|
|
828
|
+
);
|
|
829
|
+
if (errWebClipperTemplate) return errWebClipperTemplate;
|
|
830
|
+
const errWebClipperReadme = await copyTemplateOrPreserve(
|
|
831
|
+
WEB_CLIPPER_README_REL,
|
|
832
|
+
"web-clipper/readme.txt"
|
|
833
|
+
);
|
|
834
|
+
if (errWebClipperReadme) return errWebClipperReadme;
|
|
776
835
|
const err22 = await writeOrPreserve("log.md", async () => {
|
|
777
836
|
const tpl = await readFile3(join5(input.templates, "log.md"), "utf8");
|
|
778
837
|
return tpl.replace(/\{\{INIT_DATE\}\}/g, today).replace("{{DOMAIN}}", domain).replace("{{WIKI_LANG}}", canonicalLang);
|
|
@@ -804,7 +863,9 @@ async function runInit(input) {
|
|
|
804
863
|
`lang: ${canonicalLang}`,
|
|
805
864
|
`created: ${created.length}, preserved: ${preserved.length}`,
|
|
806
865
|
`discovered tags: ${discovered_tags}`,
|
|
807
|
-
skipEnv ? "env: skipped" : `env: ${envWritten}
|
|
866
|
+
skipEnv ? "env: skipped" : `env: ${envWritten}`,
|
|
867
|
+
`web clipper template: ${WEB_CLIPPER_TEMPLATE_REL}`,
|
|
868
|
+
"web clipper import: open Obsidian Web Clipper Settings and import the JSON in each browser profile"
|
|
808
869
|
].join("\n");
|
|
809
870
|
if (created.length > 0) {
|
|
810
871
|
appendLastOp(target, {
|
|
@@ -828,7 +889,11 @@ async function runInit(input) {
|
|
|
828
889
|
imported_from_hermes: importedFromHermes,
|
|
829
890
|
discovered_tags,
|
|
830
891
|
humanHint,
|
|
831
|
-
templates_created: created.includes(`${TEMPLATE_FOLDER}/tpl-ad-hoc-capture.md`)
|
|
892
|
+
templates_created: created.includes(`${TEMPLATE_FOLDER}/tpl-ad-hoc-capture.md`),
|
|
893
|
+
web_clipper_template_path: WEB_CLIPPER_TEMPLATE_REL,
|
|
894
|
+
web_clipper_readme_path: WEB_CLIPPER_README_REL,
|
|
895
|
+
web_clipper_template_created: created.includes(WEB_CLIPPER_TEMPLATE_REL),
|
|
896
|
+
web_clipper_template_preserved: preserved.includes(WEB_CLIPPER_TEMPLATE_REL)
|
|
832
897
|
})
|
|
833
898
|
};
|
|
834
899
|
}
|
|
@@ -1198,21 +1263,21 @@ function rollbackPath(vault, path, stage2, stage3) {
|
|
|
1198
1263
|
function hasMarkers(path, text) {
|
|
1199
1264
|
const lines = text.split(/\r?\n/);
|
|
1200
1265
|
let open2 = false;
|
|
1201
|
-
let
|
|
1266
|
+
let sep4 = false;
|
|
1202
1267
|
for (const line of lines) {
|
|
1203
1268
|
if (line.startsWith("<<<<<<< ")) {
|
|
1204
1269
|
open2 = true;
|
|
1205
|
-
|
|
1270
|
+
sep4 = false;
|
|
1206
1271
|
continue;
|
|
1207
1272
|
}
|
|
1208
1273
|
if (line === "=======" && open2) {
|
|
1209
|
-
|
|
1274
|
+
sep4 = true;
|
|
1210
1275
|
continue;
|
|
1211
1276
|
}
|
|
1212
|
-
if (line.startsWith(">>>>>>> ") && open2 &&
|
|
1277
|
+
if (line.startsWith(">>>>>>> ") && open2 && sep4) return true;
|
|
1213
1278
|
if (line.startsWith(">>>>>>> ")) {
|
|
1214
1279
|
open2 = false;
|
|
1215
|
-
|
|
1280
|
+
sep4 = false;
|
|
1216
1281
|
}
|
|
1217
1282
|
}
|
|
1218
1283
|
return false;
|
|
@@ -1489,16 +1554,6 @@ async function runLogMaterialize(input) {
|
|
|
1489
1554
|
// src/commands/log-migrate-legacy.ts
|
|
1490
1555
|
import { readFileSync as readFileSync6 } from "fs";
|
|
1491
1556
|
import { join as join10 } from "path";
|
|
1492
|
-
|
|
1493
|
-
// src/utils/operation-id.ts
|
|
1494
|
-
import { createHash as createHash2 } from "crypto";
|
|
1495
|
-
function operationId(namespace, parts) {
|
|
1496
|
-
const hash = createHash2("sha256").update(namespace).update("\0");
|
|
1497
|
-
for (const part of parts) hash.update(part).update("\0");
|
|
1498
|
-
return hash.digest("hex");
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
// src/commands/log-migrate-legacy.ts
|
|
1502
1557
|
var BLOCK_RE = /^## \[(\d{4}-\d{2}-\d{2})\][^\n]*$/gm;
|
|
1503
1558
|
var PUBLISH_RE = /<!--\s*skillwiki-(?:page-publish|project-page-publish):([a-f0-9]{64})\s*-->/i;
|
|
1504
1559
|
function splitLegacyBlocks(text) {
|
|
@@ -2797,7 +2852,7 @@ function selfCheckReport(report) {
|
|
|
2797
2852
|
if (lint.summary.info !== sumBySeverity("info")) {
|
|
2798
2853
|
errors.push(`lint.summary.info=${lint.summary.info} but info bucket counts sum to ${sumBySeverity("info")}`);
|
|
2799
2854
|
}
|
|
2800
|
-
for (const component of ["doctor", "lint", "vault_sync", "query_readiness", "source_freshness"]) {
|
|
2855
|
+
for (const component of ["doctor", "lint", "vault_sync", "query_readiness", "source_freshness", "source_lifecycle"]) {
|
|
2801
2856
|
if (!report.coverage[component]) errors.push(`missing coverage entry for ${component}`);
|
|
2802
2857
|
}
|
|
2803
2858
|
for (const flag of report.risk_flags) {
|
|
@@ -2817,6 +2872,7 @@ function buildHumanHint(report) {
|
|
|
2817
2872
|
}
|
|
2818
2873
|
lines.push(`vault_sync: ${report.components.vault_sync.summary.pass} pass, ${report.components.vault_sync.summary.warn} warn, ${report.components.vault_sync.summary.error} error`);
|
|
2819
2874
|
lines.push(`query_readiness: ${report.components.query_readiness.status} (${report.components.query_readiness.summary.score}/100)`);
|
|
2875
|
+
lines.push(`source_lifecycle: ${report.components.source_lifecycle.summary.pending} pending, ${report.components.source_lifecycle.summary.deferred} deferred, ${report.components.source_lifecycle.summary.reviewed_no_op} reviewed-no-op`);
|
|
2820
2876
|
if (report.risk_flags.length > 0) {
|
|
2821
2877
|
lines.push("risk flags:");
|
|
2822
2878
|
for (const flag of report.risk_flags) {
|
|
@@ -2876,7 +2932,8 @@ function buildIncompleteReport(input, syncMode) {
|
|
|
2876
2932
|
lint: { state: "unknown", status: "unknown", reason: "lint summary still running" },
|
|
2877
2933
|
vault_sync: vaultSyncCoverage,
|
|
2878
2934
|
query_readiness: { state: "unknown", status: "unknown", reason: "health run not complete" },
|
|
2879
|
-
source_freshness: { state: "unknown", status: "unknown", reason: "health run not complete" }
|
|
2935
|
+
source_freshness: { state: "unknown", status: "unknown", reason: "health run not complete" },
|
|
2936
|
+
source_lifecycle: { state: "unknown", status: "unknown", reason: "health run not complete" }
|
|
2880
2937
|
},
|
|
2881
2938
|
components: {
|
|
2882
2939
|
doctor: {
|
|
@@ -2904,6 +2961,11 @@ function buildIncompleteReport(input, syncMode) {
|
|
|
2904
2961
|
status: "unknown",
|
|
2905
2962
|
blocking: false,
|
|
2906
2963
|
summary: { stale_pages: 0, file_source_url: 0, stale_sections: 0 }
|
|
2964
|
+
},
|
|
2965
|
+
source_lifecycle: {
|
|
2966
|
+
status: "unknown",
|
|
2967
|
+
blocking: false,
|
|
2968
|
+
summary: { pending: 0, deferred: 0, reviewed_no_op: 0, archived_raw: 0, preserved_duplicates: 0, legacy_archive_raw: 0, legacy_schema: 0, invalid_schema: 0, oldest_pending: null, fresh_pending_examples: [] }
|
|
2907
2969
|
}
|
|
2908
2970
|
},
|
|
2909
2971
|
risk_flags: [],
|
|
@@ -2958,6 +3020,26 @@ async function runHealth(input) {
|
|
|
2958
3020
|
stale_sections: bucketCount(lint.result.data.buckets, "stale_sections")
|
|
2959
3021
|
}
|
|
2960
3022
|
};
|
|
3023
|
+
const sourceInventory = await inventorySources({ vault: input.vault });
|
|
3024
|
+
const sourceItems = sourceInventory.output?.items ?? [];
|
|
3025
|
+
const pendingItems = sourceItems.filter((item) => item.lifecycle_status === "pending" && item.storage_status === "active");
|
|
3026
|
+
const pendingDates = pendingItems.map((item) => item.captured).filter((value) => value !== null).sort();
|
|
3027
|
+
const sourceLifecycle = {
|
|
3028
|
+
status: pendingItems.length > 0 ? "info" : "pass",
|
|
3029
|
+
blocking: false,
|
|
3030
|
+
summary: {
|
|
3031
|
+
pending: pendingItems.length,
|
|
3032
|
+
deferred: sourceItems.filter((item) => item.lifecycle_status === "deferred").length,
|
|
3033
|
+
reviewed_no_op: sourceItems.filter((item) => item.lifecycle_status === "reviewed-no-op").length,
|
|
3034
|
+
archived_raw: sourceItems.filter((item) => item.storage_status === "archived").length,
|
|
3035
|
+
preserved_duplicates: sourceItems.filter((item) => item.storage_status === "duplicate").length,
|
|
3036
|
+
legacy_archive_raw: sourceItems.filter((item) => item.storage_status === "legacy-archived").length,
|
|
3037
|
+
legacy_schema: sourceItems.filter((item) => item.schema_status === "legacy").length,
|
|
3038
|
+
invalid_schema: sourceItems.filter((item) => item.schema_status === "invalid").length,
|
|
3039
|
+
oldest_pending: pendingDates[0] ?? null,
|
|
3040
|
+
fresh_pending_examples: pendingItems.filter((item) => item.age_bucket === "fresh").slice(0, 3).map((item) => item.raw_path)
|
|
3041
|
+
}
|
|
3042
|
+
};
|
|
2961
3043
|
const doctorComponent = {
|
|
2962
3044
|
status: doctorStatus,
|
|
2963
3045
|
blocking: false,
|
|
@@ -3002,14 +3084,16 @@ async function runHealth(input) {
|
|
|
3002
3084
|
lint: { state: "checked", status: lintComponent.status },
|
|
3003
3085
|
vault_sync: vaultSyncCoverage,
|
|
3004
3086
|
query_readiness: { state: "checked", status: queryReadiness.status },
|
|
3005
|
-
source_freshness: { state: "checked", status: sourceFreshness.status }
|
|
3087
|
+
source_freshness: { state: "checked", status: sourceFreshness.status },
|
|
3088
|
+
source_lifecycle: { state: "checked", status: sourceLifecycle.status }
|
|
3006
3089
|
},
|
|
3007
3090
|
components: {
|
|
3008
3091
|
doctor: doctorComponent,
|
|
3009
3092
|
lint: lintComponent,
|
|
3010
3093
|
vault_sync: vaultSync,
|
|
3011
3094
|
query_readiness: queryReadiness,
|
|
3012
|
-
source_freshness: sourceFreshness
|
|
3095
|
+
source_freshness: sourceFreshness,
|
|
3096
|
+
source_lifecycle: sourceLifecycle
|
|
3013
3097
|
},
|
|
3014
3098
|
risk_flags: riskFlags,
|
|
3015
3099
|
details_included: false,
|
|
@@ -3056,7 +3140,7 @@ async function runHealth(input) {
|
|
|
3056
3140
|
}
|
|
3057
3141
|
|
|
3058
3142
|
// src/commands/archive.ts
|
|
3059
|
-
import { rename as rename2, mkdir as mkdir6, readFile as readFile7
|
|
3143
|
+
import { rename as rename2, mkdir as mkdir6, readFile as readFile7 } from "fs/promises";
|
|
3060
3144
|
import { join as join18, dirname as dirname5 } from "path";
|
|
3061
3145
|
|
|
3062
3146
|
// src/utils/delete-intent.ts
|
|
@@ -3127,18 +3211,21 @@ async function runArchive(input) {
|
|
|
3127
3211
|
let relPath = lookup(scan.data.typedKnowledge);
|
|
3128
3212
|
let isRaw = false;
|
|
3129
3213
|
if (!relPath) {
|
|
3130
|
-
relPath = lookup(scan.data.raw);
|
|
3214
|
+
relPath = input.page.includes("/") ? lookup(scan.data.raw) : void 0;
|
|
3131
3215
|
isRaw = relPath != null;
|
|
3132
3216
|
}
|
|
3133
3217
|
if (!relPath) return { exitCode: ExitCode.ARCHIVE_TARGET_NOT_FOUND, result: err("ARCHIVE_TARGET_NOT_FOUND", { page: input.page }) };
|
|
3134
3218
|
if (relPath.startsWith("_archive/")) return { exitCode: ExitCode.ARCHIVE_ALREADY_ARCHIVED, result: err("ARCHIVE_ALREADY_ARCHIVED", { page: relPath }) };
|
|
3135
3219
|
const slug = relPath.replace(/\.md$/, "").split("/").pop();
|
|
3136
|
-
const
|
|
3220
|
+
const rawArchive = isRaw ? lifecycleDestination(relPath, "archive") : void 0;
|
|
3221
|
+
if (rawArchive && !rawArchive.ok) return { exitCode: ExitCode.USAGE, result: rawArchive };
|
|
3222
|
+
const archivePath = isRaw ? rawArchive.data : join18("_archive", relPath).replace(/\\/g, "/");
|
|
3137
3223
|
const remoteRoot = normalizeRemoteRoot(input.remote);
|
|
3138
3224
|
const remoteObjectPath = buildRemoteObjectPath(remoteRoot, relPath);
|
|
3139
3225
|
let cascade;
|
|
3140
|
-
if (input.cascade) {
|
|
3226
|
+
if (input.cascade || isRaw) {
|
|
3141
3227
|
const wikilinkRefs = [];
|
|
3228
|
+
const citationRefs = [];
|
|
3142
3229
|
const sourceArrayRefs = [];
|
|
3143
3230
|
for (const page of scan.data.typedKnowledge) {
|
|
3144
3231
|
if (page.relPath === relPath) continue;
|
|
@@ -3147,12 +3234,15 @@ async function runArchive(input) {
|
|
|
3147
3234
|
if (!split.ok) continue;
|
|
3148
3235
|
const wl = countWikilinks(split.data.body, slug);
|
|
3149
3236
|
if (wl > 0) wikilinkRefs.push({ page: page.relPath, count: wl });
|
|
3237
|
+
const rewritten = isRaw ? rewriteRawSourceReferences(text, relPath, archivePath) : void 0;
|
|
3238
|
+
const citationCount = rewritten?.bodyCitationCount ?? 0;
|
|
3239
|
+
if (citationCount > 0) citationRefs.push({ page: page.relPath, count: citationCount });
|
|
3150
3240
|
const fm = extractFrontmatter(text);
|
|
3151
3241
|
if (!fm.ok) continue;
|
|
3152
3242
|
const sources = fm.data.sources;
|
|
3153
|
-
if (Array.isArray(sources) && sources.includes(relPath)) {
|
|
3243
|
+
if (Array.isArray(sources) && (isRaw ? rewritten.sourcesBefore.some((source, index) => source !== rewritten.sourcesAfter[index]) : sources.includes(relPath))) {
|
|
3154
3244
|
const before = sources.filter((s) => typeof s === "string");
|
|
3155
|
-
const after = before.filter((s) => s !== relPath);
|
|
3245
|
+
const after = isRaw ? rewritten.sourcesAfter : before.filter((s) => s !== relPath);
|
|
3156
3246
|
sourceArrayRefs.push({ page: page.relPath, sources_before: before, sources_after: after });
|
|
3157
3247
|
}
|
|
3158
3248
|
}
|
|
@@ -3167,9 +3257,23 @@ async function runArchive(input) {
|
|
|
3167
3257
|
if (e instanceof Error && "code" in e && e.code !== "ENOENT") throw e;
|
|
3168
3258
|
}
|
|
3169
3259
|
}
|
|
3170
|
-
cascade = { wikilink_refs: wikilinkRefs, index_refs: indexRefs, source_array_refs: sourceArrayRefs };
|
|
3171
|
-
}
|
|
3172
|
-
|
|
3260
|
+
cascade = { wikilink_refs: wikilinkRefs, citation_refs: citationRefs, index_refs: indexRefs, source_array_refs: sourceArrayRefs };
|
|
3261
|
+
}
|
|
3262
|
+
let rawPlan;
|
|
3263
|
+
if (isRaw) {
|
|
3264
|
+
rawPlan = await planRawStructuralMove({ vault: input.vault, operation: "archive", source: relPath, destination: archivePath });
|
|
3265
|
+
if (!rawPlan.ok) return { exitCode: ExitCode.WRITE_FAILED, result: rawPlan };
|
|
3266
|
+
}
|
|
3267
|
+
const citationState = isRaw ? await snapshotMaintainedPageState(scan.data.allMarkdown) : [];
|
|
3268
|
+
const rawApprovalToken = rawPlan?.ok ? operationId("raw-archive-approval", [
|
|
3269
|
+
rawPlan.data.operation_id,
|
|
3270
|
+
rawPlan.data.approval_token,
|
|
3271
|
+
rawPlan.data.source_sha256,
|
|
3272
|
+
archivePath,
|
|
3273
|
+
...citationState
|
|
3274
|
+
]) : void 0;
|
|
3275
|
+
const rawStructuralApproval = rawPlan?.ok ? rawPlan.data.approval_token : void 0;
|
|
3276
|
+
if ((input.cascade || isRaw) && !input.apply) {
|
|
3173
3277
|
const summary = `DRY-RUN \u2014 would archive ${relPath}; ${cascade.wikilink_refs.length} wikilink ref(s), ${cascade.index_refs.length} index ref(s), ${cascade.source_array_refs.length} source array ref(s).`;
|
|
3174
3278
|
return {
|
|
3175
3279
|
exitCode: ExitCode.OK,
|
|
@@ -3178,37 +3282,73 @@ async function runArchive(input) {
|
|
|
3178
3282
|
archived_to: archivePath,
|
|
3179
3283
|
index_updated: false,
|
|
3180
3284
|
applied: false,
|
|
3285
|
+
...rawApprovalToken ? { approval_token: rawApprovalToken } : {},
|
|
3181
3286
|
cascade,
|
|
3182
3287
|
...remoteObjectPath ? { remote: { plannedDeletes: [remoteObjectPath], deleted: [] } } : {},
|
|
3183
3288
|
humanHint: summary + (remoteObjectPath ? ` (remote planned ${input.remoteDelete ? "delete" : "preview"}: ${remoteObjectPath})` : "")
|
|
3184
3289
|
})
|
|
3185
3290
|
};
|
|
3186
3291
|
}
|
|
3187
|
-
if (
|
|
3292
|
+
if (isRaw) {
|
|
3293
|
+
if (!input.approve || !rawStructuralApproval || input.approve !== rawApprovalToken) {
|
|
3294
|
+
return { exitCode: ExitCode.USAGE, result: err("APPROVAL_INVALID", { message: "raw archive requires the live dry-run --approve token" }) };
|
|
3295
|
+
}
|
|
3296
|
+
const moved = await applyRawStructuralMove({
|
|
3297
|
+
vault: input.vault,
|
|
3298
|
+
operation: "archive",
|
|
3299
|
+
source: relPath,
|
|
3300
|
+
destination: archivePath,
|
|
3301
|
+
approve: rawStructuralApproval,
|
|
3302
|
+
command: "skillwiki archive",
|
|
3303
|
+
citationChanges: [.../* @__PURE__ */ new Set([...cascade?.source_array_refs.map((ref) => ref.page) ?? [], ...cascade?.citation_refs.map((ref) => ref.page) ?? []])]
|
|
3304
|
+
});
|
|
3305
|
+
if (!moved.ok) return { exitCode: ExitCode.WRITE_FAILED, result: moved };
|
|
3306
|
+
}
|
|
3307
|
+
if ((input.cascade || isRaw) && input.apply && cascade) {
|
|
3188
3308
|
for (const ref of cascade.source_array_refs) {
|
|
3189
3309
|
const absPath = join18(input.vault, ref.page);
|
|
3190
3310
|
const text = await readFile7(absPath, "utf8");
|
|
3191
|
-
const split = splitFrontmatter(text);
|
|
3192
|
-
if (!split.ok) continue;
|
|
3193
|
-
const before = split.data.rawFrontmatter;
|
|
3194
|
-
const newSourcesYaml = ref.sources_after.length === 0 ? "sources: []" : "sources:\n" + ref.sources_after.map((s) => ` - ${s}`).join("\n");
|
|
3195
|
-
const fmRewritten = before.replace(
|
|
3196
|
-
/^sources:\s*(?:\[[^\]]*\]|(?:\r?\n(?:\s*-\s.*))+)/m,
|
|
3197
|
-
newSourcesYaml
|
|
3198
|
-
);
|
|
3199
|
-
if (fmRewritten === before) continue;
|
|
3200
3311
|
if (!arraysEqual(ref.sources_after, ref.sources_before)) {
|
|
3201
|
-
|
|
3312
|
+
let updated;
|
|
3313
|
+
if (isRaw) {
|
|
3314
|
+
updated = rewriteRawSourceReferences(text, relPath, archivePath).text;
|
|
3315
|
+
} else {
|
|
3316
|
+
const split = splitFrontmatter(text);
|
|
3317
|
+
if (!split.ok) continue;
|
|
3318
|
+
const newSourcesYaml = ref.sources_after.length === 0 ? "sources: []" : "sources:\n" + ref.sources_after.map((s) => ` - ${s}`).join("\n");
|
|
3319
|
+
const fmRewritten = split.data.rawFrontmatter.replace(
|
|
3320
|
+
/^sources:\s*(?:\[[^\]]*\]|(?:\r?\n(?:\s*-\s.*))+)/m,
|
|
3321
|
+
newSourcesYaml
|
|
3322
|
+
);
|
|
3323
|
+
if (fmRewritten === split.data.rawFrontmatter) continue;
|
|
3324
|
+
updated = `---
|
|
3202
3325
|
${fmRewritten}
|
|
3203
|
-
---${split.data.body}
|
|
3326
|
+
---${split.data.body}`;
|
|
3327
|
+
}
|
|
3328
|
+
const write = await safeWritePage(absPath, updated);
|
|
3329
|
+
if (!write.ok) return { exitCode: ExitCode.WRITE_FAILED, result: write };
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
if (isRaw) {
|
|
3333
|
+
for (const ref of cascade.citation_refs) {
|
|
3334
|
+
if (cascade.source_array_refs.some((sourceRef) => sourceRef.page === ref.page)) continue;
|
|
3335
|
+
const absPath = join18(input.vault, ref.page);
|
|
3336
|
+
const original = await readFile7(absPath, "utf8");
|
|
3337
|
+
const updated = rewriteRawSourceReferences(original, relPath, archivePath).text;
|
|
3338
|
+
if (updated !== original) {
|
|
3339
|
+
const write = await safeWritePage(absPath, updated);
|
|
3340
|
+
if (!write.ok) return { exitCode: ExitCode.WRITE_FAILED, result: write };
|
|
3341
|
+
}
|
|
3204
3342
|
}
|
|
3205
3343
|
}
|
|
3206
3344
|
}
|
|
3207
|
-
|
|
3208
|
-
|
|
3345
|
+
if (!isRaw) {
|
|
3346
|
+
await mkdir6(dirname5(join18(input.vault, archivePath)), { recursive: true });
|
|
3347
|
+
await rename2(join18(input.vault, relPath), join18(input.vault, archivePath));
|
|
3348
|
+
}
|
|
3209
3349
|
let indexUpdated = false;
|
|
3210
3350
|
if (!isRaw) {
|
|
3211
|
-
const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-
|
|
3351
|
+
const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-FRWTZB5Y.js");
|
|
3212
3352
|
const before = await readFile7(join18(input.vault, "index.md"), "utf8").catch(() => "");
|
|
3213
3353
|
const fullTarget = relPath.replace(/\.md$/, "");
|
|
3214
3354
|
const bare = fullTarget.split("/").pop() ?? fullTarget;
|
|
@@ -3241,7 +3381,7 @@ ${fmRewritten}
|
|
|
3241
3381
|
}
|
|
3242
3382
|
remote = pruned.data;
|
|
3243
3383
|
}
|
|
3244
|
-
const applied = input.cascade ? true : void 0;
|
|
3384
|
+
const applied = input.cascade || isRaw ? true : void 0;
|
|
3245
3385
|
const cascadeNote = input.cascade ? ` (cascade: ${cascade.source_array_refs.length} src arrays updated, ${cascade.wikilink_refs.length} wikilinks reported)` : "";
|
|
3246
3386
|
const remoteNote = remote ? ` (remote ${input.remoteDelete ? `deleted ${remote.deleted.length}` : `planned ${remote.plannedDeletes.length}`})` : "";
|
|
3247
3387
|
return {
|
|
@@ -3298,6 +3438,15 @@ async function runRemove(input) {
|
|
|
3298
3438
|
result: err("FILE_NOT_FOUND", { page: input.page })
|
|
3299
3439
|
};
|
|
3300
3440
|
}
|
|
3441
|
+
if (relPath.startsWith("raw/")) {
|
|
3442
|
+
return {
|
|
3443
|
+
exitCode: ExitCode.USAGE,
|
|
3444
|
+
result: err("RAW_DISPOSAL_REQUIRES_EXACT_TARGET_APPROVAL", {
|
|
3445
|
+
path: relPath,
|
|
3446
|
+
message: "ordinary remove never deletes raw evidence; use sources dispose with an exact target and approved preview"
|
|
3447
|
+
})
|
|
3448
|
+
};
|
|
3449
|
+
}
|
|
3301
3450
|
if (relPath.startsWith("_archive/")) {
|
|
3302
3451
|
return {
|
|
3303
3452
|
exitCode: ExitCode.USAGE,
|
|
@@ -3317,10 +3466,10 @@ async function runRemove(input) {
|
|
|
3317
3466
|
const tombstonePath = await writeDeleteIntent(input.vault, intent);
|
|
3318
3467
|
await unlink2(join19(input.vault, relPath));
|
|
3319
3468
|
if (relPath.endsWith(".md") && !relPath.startsWith("raw/")) {
|
|
3320
|
-
const { readFile:
|
|
3469
|
+
const { readFile: readFile20 } = await import("fs/promises");
|
|
3321
3470
|
const { join: pathJoin } = await import("path");
|
|
3322
|
-
const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-
|
|
3323
|
-
const before = await
|
|
3471
|
+
const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-FRWTZB5Y.js");
|
|
3472
|
+
const before = await readFile20(pathJoin(input.vault, "index.md"), "utf8").catch(() => "");
|
|
3324
3473
|
const fullTarget = relPath.replace(/\.md$/, "");
|
|
3325
3474
|
const bare = fullTarget.split("/").pop() ?? fullTarget;
|
|
3326
3475
|
const hadIndexEntry = before.includes(`[[${fullTarget}]]`) || before.includes(`[[${bare}]]`);
|
|
@@ -3358,7 +3507,7 @@ async function runRemove(input) {
|
|
|
3358
3507
|
}
|
|
3359
3508
|
|
|
3360
3509
|
// src/commands/drift.ts
|
|
3361
|
-
import { createHash as
|
|
3510
|
+
import { createHash as createHash2 } from "crypto";
|
|
3362
3511
|
|
|
3363
3512
|
// src/utils/fetch.ts
|
|
3364
3513
|
async function controlledFetch(url, opts) {
|
|
@@ -3437,7 +3586,7 @@ async function runDrift(input) {
|
|
|
3437
3586
|
});
|
|
3438
3587
|
continue;
|
|
3439
3588
|
}
|
|
3440
|
-
const currentHash =
|
|
3589
|
+
const currentHash = createHash2("sha256").update(Buffer.from(resp.data.body, "utf8")).digest("hex");
|
|
3441
3590
|
const identity = assessSourceIdentity({
|
|
3442
3591
|
rawPath: raw.relPath,
|
|
3443
3592
|
sourceUrl,
|
|
@@ -3455,29 +3604,13 @@ async function runDrift(input) {
|
|
|
3455
3604
|
continue;
|
|
3456
3605
|
}
|
|
3457
3606
|
const drifted2 = currentHash !== storedHash;
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
results.push({
|
|
3466
|
-
raw_path: raw.relPath,
|
|
3467
|
-
source_url: sourceUrl,
|
|
3468
|
-
stored_sha256: storedHash,
|
|
3469
|
-
current_sha256: currentHash,
|
|
3470
|
-
status: "updated"
|
|
3471
|
-
});
|
|
3472
|
-
} else {
|
|
3473
|
-
results.push({
|
|
3474
|
-
raw_path: raw.relPath,
|
|
3475
|
-
source_url: sourceUrl,
|
|
3476
|
-
stored_sha256: storedHash,
|
|
3477
|
-
current_sha256: currentHash,
|
|
3478
|
-
status: drifted2 ? "drifted" : "unchanged"
|
|
3479
|
-
});
|
|
3480
|
-
}
|
|
3607
|
+
results.push({
|
|
3608
|
+
raw_path: raw.relPath,
|
|
3609
|
+
source_url: sourceUrl,
|
|
3610
|
+
stored_sha256: storedHash,
|
|
3611
|
+
current_sha256: currentHash,
|
|
3612
|
+
status: drifted2 ? "drifted" : "unchanged"
|
|
3613
|
+
});
|
|
3481
3614
|
}
|
|
3482
3615
|
const drifted = results.filter((r) => r.status === "drifted");
|
|
3483
3616
|
const fetchFailed = results.filter((r) => r.status === "fetch_failed");
|
|
@@ -3496,14 +3629,7 @@ ${body}`;
|
|
|
3496
3629
|
}
|
|
3497
3630
|
if (fetchFailed.length > 0) hintLines.push(`fetch_failed: ${fetchFailed.length}`, ...fetchFailed.map((f) => ` ${f.raw_path}: ${f.fetch_error}`));
|
|
3498
3631
|
if (updated.length > 0) hintLines.push(`updated: ${updated.length}`, ...updated.map((u) => ` ${u.raw_path}`));
|
|
3499
|
-
if (input.apply &&
|
|
3500
|
-
appendLastOp(input.vault, {
|
|
3501
|
-
operation: "drift-apply",
|
|
3502
|
-
summary: `updated ${updated.length} raw sources`,
|
|
3503
|
-
files: updated.map((u) => u.raw_path),
|
|
3504
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3505
|
-
});
|
|
3506
|
-
}
|
|
3632
|
+
if (input.apply && drifted.length > 0) hintLines.push("raw evidence is immutable; use attended reingest to create a new capture and archive the prior bytes");
|
|
3507
3633
|
return {
|
|
3508
3634
|
exitCode,
|
|
3509
3635
|
result: ok({ scanned: results.length, drifted, fetch_failed: fetchFailed, identity_conflicts: identityConflicts, updated, newFiles: newResults, unchanged, humanHint: hintLines.join("\n") })
|
|
@@ -3977,7 +4103,7 @@ async function runTranscripts(input) {
|
|
|
3977
4103
|
}
|
|
3978
4104
|
|
|
3979
4105
|
// src/commands/compound.ts
|
|
3980
|
-
import { writeFile as
|
|
4106
|
+
import { writeFile as writeFile6, mkdir as mkdir7, readdir as readdir6, unlink as unlink3 } from "fs/promises";
|
|
3981
4107
|
import { join as join23 } from "path";
|
|
3982
4108
|
import { existsSync as existsSync7 } from "fs";
|
|
3983
4109
|
import { readFile as readFile10 } from "fs/promises";
|
|
@@ -4138,7 +4264,7 @@ async function runCompound(input) {
|
|
|
4138
4264
|
if (!existsSync7(compoundDir)) {
|
|
4139
4265
|
await mkdir7(compoundDir, { recursive: true });
|
|
4140
4266
|
}
|
|
4141
|
-
await
|
|
4267
|
+
await writeFile6(compoundPath, content, "utf8");
|
|
4142
4268
|
}
|
|
4143
4269
|
promoted.push(`${slug}.md`);
|
|
4144
4270
|
}
|
|
@@ -4262,7 +4388,7 @@ no compound entries found`;
|
|
|
4262
4388
|
}
|
|
4263
4389
|
|
|
4264
4390
|
// src/commands/session-brief.ts
|
|
4265
|
-
import { mkdir as mkdir8, readFile as readFile11, writeFile as
|
|
4391
|
+
import { mkdir as mkdir8, readFile as readFile11, writeFile as writeFile7 } from "fs/promises";
|
|
4266
4392
|
import { join as join24, relative, sep } from "path";
|
|
4267
4393
|
var MAX_WORDS = 900;
|
|
4268
4394
|
async function runSessionBrief(input) {
|
|
@@ -4282,7 +4408,8 @@ async function runSessionBrief(input) {
|
|
|
4282
4408
|
baseHealthWarnings,
|
|
4283
4409
|
satelliteHealth,
|
|
4284
4410
|
sessionPins,
|
|
4285
|
-
memoryTopics
|
|
4411
|
+
memoryTopics,
|
|
4412
|
+
pendingSources
|
|
4286
4413
|
] = await Promise.all([
|
|
4287
4414
|
loadTranscriptInfo(scan.data.raw),
|
|
4288
4415
|
loadWorkItems(scan.data.workItems),
|
|
@@ -4290,7 +4417,8 @@ async function runSessionBrief(input) {
|
|
|
4290
4417
|
loadHealthWarnings(input.vault),
|
|
4291
4418
|
loadSatelliteHealth(input.vault),
|
|
4292
4419
|
loadSessionPins(input.vault, project),
|
|
4293
|
-
project ? loadMemoryTopics(input.vault, project) : Promise.resolve([])
|
|
4420
|
+
project ? loadMemoryTopics(input.vault, project) : Promise.resolve([]),
|
|
4421
|
+
loadPendingSources(input.vault, today)
|
|
4294
4422
|
]);
|
|
4295
4423
|
const healthWarnings = [
|
|
4296
4424
|
...baseHealthWarnings,
|
|
@@ -4313,6 +4441,7 @@ async function runSessionBrief(input) {
|
|
|
4313
4441
|
sessionPins,
|
|
4314
4442
|
latestLogs,
|
|
4315
4443
|
unclaimedCaptures,
|
|
4444
|
+
pendingSources,
|
|
4316
4445
|
activeWork,
|
|
4317
4446
|
latestDigest,
|
|
4318
4447
|
projectLogs,
|
|
@@ -4347,6 +4476,7 @@ async function runSessionBrief(input) {
|
|
|
4347
4476
|
log_updated: logUpdated,
|
|
4348
4477
|
generated_at: generatedAt,
|
|
4349
4478
|
session_pins: sessionPins,
|
|
4479
|
+
pending_sources: pendingSources,
|
|
4350
4480
|
memory_topics: memoryTopics,
|
|
4351
4481
|
humanHint
|
|
4352
4482
|
})
|
|
@@ -4412,6 +4542,17 @@ async function loadTranscriptInfo(rawPages) {
|
|
|
4412
4542
|
}
|
|
4413
4543
|
return out;
|
|
4414
4544
|
}
|
|
4545
|
+
async function loadPendingSources(vault, today) {
|
|
4546
|
+
const inventory = await inventorySources({ vault, today });
|
|
4547
|
+
if (!inventory.output) return [];
|
|
4548
|
+
return inventory.output.items.filter((item) => item.storage_status === "active" && item.lifecycle_status === "pending").slice(0, 5).map((item) => ({
|
|
4549
|
+
path: item.raw_path,
|
|
4550
|
+
title: item.title,
|
|
4551
|
+
summary: item.source_url ?? `${item.schema_status} source awaiting integration`,
|
|
4552
|
+
date: item.captured ?? "",
|
|
4553
|
+
status: item.schema_status
|
|
4554
|
+
}));
|
|
4555
|
+
}
|
|
4415
4556
|
async function loadWorkItems(workItemPages) {
|
|
4416
4557
|
const out = [];
|
|
4417
4558
|
for (const page of workItemPages.filter((p) => p.relPath.endsWith("/spec.md"))) {
|
|
@@ -4482,6 +4623,7 @@ function renderBrief(input) {
|
|
|
4482
4623
|
appendPinnedContextSection(lines, input.sessionPins);
|
|
4483
4624
|
appendSection(lines, "Active Work", input.activeWork, "No active project work found.");
|
|
4484
4625
|
appendSection(lines, "Unclaimed Captures", input.unclaimedCaptures, "No unclaimed task or bug captures found.");
|
|
4626
|
+
appendSection(lines, "Recent Pending Sources", input.pendingSources, "No pending articles or papers found.");
|
|
4485
4627
|
appendSection(lines, "Recent Session Logs", input.project ? input.projectLogs : input.latestLogs, "No recent session logs found.");
|
|
4486
4628
|
appendSection(lines, "Latest Agent Memory Trends", input.latestDigest, "No agent memory trends digest found.");
|
|
4487
4629
|
appendMemoryTopicsSection(lines, input.project, input.memoryTopics);
|
|
@@ -4492,6 +4634,7 @@ function renderBrief(input) {
|
|
|
4492
4634
|
"- `skillwiki status`",
|
|
4493
4635
|
input.project ? `- \`skillwiki project-index ${input.project}\`` : '- `skillwiki query "active work"`',
|
|
4494
4636
|
"- `skillwiki transcripts --since <date>`",
|
|
4637
|
+
"- `skillwiki sources pending`",
|
|
4495
4638
|
""
|
|
4496
4639
|
);
|
|
4497
4640
|
return lines.join("\n").trimEnd() + "\n";
|
|
@@ -4613,9 +4756,9 @@ async function writeBriefArtifacts(vault, input) {
|
|
|
4613
4756
|
const previousComparable = comparableBrief(await readIfExists(metaPath));
|
|
4614
4757
|
const nextComparable = comparableBrief(committed);
|
|
4615
4758
|
const materialChange = previousComparable !== nextComparable;
|
|
4616
|
-
await
|
|
4617
|
-
await
|
|
4618
|
-
await
|
|
4759
|
+
await writeFile7(metaPath, committed, "utf8");
|
|
4760
|
+
await writeFile7(cacheMdPath, input.brief, "utf8");
|
|
4761
|
+
await writeFile7(cacheJsonPath, `${JSON.stringify({
|
|
4619
4762
|
project: input.project,
|
|
4620
4763
|
brief: input.brief,
|
|
4621
4764
|
word_count: input.wordCount,
|
|
@@ -4732,7 +4875,7 @@ function dateFromPath(path) {
|
|
|
4732
4875
|
// src/commands/ingest.ts
|
|
4733
4876
|
import { readFile as readFile13, open, unlink as unlink4, mkdir as mkdir9 } from "fs/promises";
|
|
4734
4877
|
import { join as join26 } from "path";
|
|
4735
|
-
import { createHash as
|
|
4878
|
+
import { createHash as createHash4 } from "crypto";
|
|
4736
4879
|
|
|
4737
4880
|
// src/commands/page-publish.ts
|
|
4738
4881
|
import { readFileSync as readFileSync12, realpathSync } from "fs";
|
|
@@ -4740,13 +4883,13 @@ import { readFile as readFile12 } from "fs/promises";
|
|
|
4740
4883
|
import { join as join25, resolve as resolve3 } from "path";
|
|
4741
4884
|
|
|
4742
4885
|
// src/utils/publication-approval.ts
|
|
4743
|
-
import { createHash as
|
|
4886
|
+
import { createHash as createHash3 } from "crypto";
|
|
4744
4887
|
var APPROVAL_CONTRACT = "skillwiki-publication-approval-v1";
|
|
4745
4888
|
var APPROVAL_TOKEN_VERSION = "swpub1";
|
|
4746
4889
|
var SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
4747
4890
|
var PUBLISHERS = /* @__PURE__ */ new Set(["page", "project-page"]);
|
|
4748
4891
|
function sha256Hex(bytes) {
|
|
4749
|
-
return
|
|
4892
|
+
return createHash3("sha256").update(bytes).digest("hex");
|
|
4750
4893
|
}
|
|
4751
4894
|
function normalizeLogNote(note) {
|
|
4752
4895
|
if (note === void 0) return "";
|
|
@@ -5545,13 +5688,13 @@ function isUrl(source) {
|
|
|
5545
5688
|
function todayIso() {
|
|
5546
5689
|
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5547
5690
|
}
|
|
5548
|
-
function buildRawContent(sourceUrl, ingested,
|
|
5691
|
+
function buildRawContent(sourceUrl, ingested, sha2562, body) {
|
|
5549
5692
|
const lines = [
|
|
5550
5693
|
"---",
|
|
5551
5694
|
sourceUrl !== null ? `source_url: "${sourceUrl}"` : "source_url:",
|
|
5552
5695
|
`created: ${ingested}`,
|
|
5553
5696
|
`ingested: ${ingested}`,
|
|
5554
|
-
`sha256: ${
|
|
5697
|
+
`sha256: ${sha2562}`,
|
|
5555
5698
|
`ingested_by: wiki-ingest`,
|
|
5556
5699
|
"---",
|
|
5557
5700
|
"",
|
|
@@ -5757,7 +5900,7 @@ async function runIngest(input, pagePublishDeps = defaultPagePublishDeps()) {
|
|
|
5757
5900
|
})
|
|
5758
5901
|
};
|
|
5759
5902
|
}
|
|
5760
|
-
const
|
|
5903
|
+
const sha2562 = createHash4("sha256").update(Buffer.from(sourceContent, "utf8")).digest("hex");
|
|
5761
5904
|
const today = todayIso();
|
|
5762
5905
|
const slug = slugify2(input.title);
|
|
5763
5906
|
const tags = input.tags && input.tags.length > 0 ? input.tags : [];
|
|
@@ -5788,7 +5931,7 @@ async function runIngest(input, pagePublishDeps = defaultPagePublishDeps()) {
|
|
|
5788
5931
|
path: rawAbsPath,
|
|
5789
5932
|
sourceUrl,
|
|
5790
5933
|
sourceContent,
|
|
5791
|
-
sha256,
|
|
5934
|
+
sha256: sha2562,
|
|
5792
5935
|
today
|
|
5793
5936
|
});
|
|
5794
5937
|
if (!resolvedRaw.ok) {
|
|
@@ -5845,11 +5988,11 @@ async function runIngest(input, pagePublishDeps = defaultPagePublishDeps()) {
|
|
|
5845
5988
|
result: ok({
|
|
5846
5989
|
raw_path: rawRelPath,
|
|
5847
5990
|
typed_path: typedRelPath,
|
|
5848
|
-
sha256,
|
|
5991
|
+
sha256: sha2562,
|
|
5849
5992
|
dry_run: true,
|
|
5850
5993
|
humanHint: [
|
|
5851
5994
|
"DRY RUN \u2014 would create:",
|
|
5852
|
-
` ${rawRelPath} (sha256: ${
|
|
5995
|
+
` ${rawRelPath} (sha256: ${sha2562.slice(0, 12)}...)`,
|
|
5853
5996
|
` ${typedRelPath}`,
|
|
5854
5997
|
` type: ${input.type}, tags: [${tags.join(", ")}]`,
|
|
5855
5998
|
input.provenance ? ` provenance: ${input.provenance}` : ""
|
|
@@ -5877,7 +6020,7 @@ async function runIngest(input, pagePublishDeps = defaultPagePublishDeps()) {
|
|
|
5877
6020
|
path: rawAbsPath,
|
|
5878
6021
|
sourceUrl,
|
|
5879
6022
|
sourceContent,
|
|
5880
|
-
sha256,
|
|
6023
|
+
sha256: sha2562,
|
|
5881
6024
|
today,
|
|
5882
6025
|
capture: resolvedRaw.data
|
|
5883
6026
|
});
|
|
@@ -5944,11 +6087,11 @@ async function runIngest(input, pagePublishDeps = defaultPagePublishDeps()) {
|
|
|
5944
6087
|
result: ok({
|
|
5945
6088
|
raw_path: rawRelPath,
|
|
5946
6089
|
typed_path: typedRelPath,
|
|
5947
|
-
sha256,
|
|
6090
|
+
sha256: sha2562,
|
|
5948
6091
|
dry_run: false,
|
|
5949
6092
|
humanHint: [
|
|
5950
6093
|
"created:",
|
|
5951
|
-
` ${rawRelPath} (sha256: ${
|
|
6094
|
+
` ${rawRelPath} (sha256: ${sha2562.slice(0, 12)}...)`,
|
|
5952
6095
|
` ${typedRelPath}`
|
|
5953
6096
|
].join("\n"),
|
|
5954
6097
|
publication: published.result.data
|
|
@@ -8128,7 +8271,7 @@ function runSyncJournalClearStale(input) {
|
|
|
8128
8271
|
|
|
8129
8272
|
// src/commands/snapshot-maintenance.ts
|
|
8130
8273
|
import { existsSync as existsSync10, mkdirSync as mkdirSync4, appendFileSync } from "fs";
|
|
8131
|
-
import { createHash as
|
|
8274
|
+
import { createHash as createHash5 } from "crypto";
|
|
8132
8275
|
import { execSync as execSync3, spawn } from "child_process";
|
|
8133
8276
|
import { platform as platform2 } from "os";
|
|
8134
8277
|
import { join as join31, resolve as resolvePath2 } from "path";
|
|
@@ -8153,7 +8296,7 @@ function computeApprovalId(plan) {
|
|
|
8153
8296
|
targets,
|
|
8154
8297
|
normalizeReason(plan.operator_reason)
|
|
8155
8298
|
].join("|");
|
|
8156
|
-
return "smap1-" +
|
|
8299
|
+
return "smap1-" + createHash5("sha256").update(payload).digest("hex").slice(0, 32);
|
|
8157
8300
|
}
|
|
8158
8301
|
function normalizeReason(reason) {
|
|
8159
8302
|
return reason.trim().replace(/\s+/g, " ");
|
|
@@ -8700,7 +8843,7 @@ async function runStatus(input) {
|
|
|
8700
8843
|
}
|
|
8701
8844
|
|
|
8702
8845
|
// src/commands/seed.ts
|
|
8703
|
-
import { mkdir as mkdir11, writeFile as
|
|
8846
|
+
import { mkdir as mkdir11, writeFile as writeFile8, stat as stat4 } from "fs/promises";
|
|
8704
8847
|
import { join as join34 } from "path";
|
|
8705
8848
|
var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
8706
8849
|
var EXAMPLE_PAGES = {
|
|
@@ -8783,7 +8926,7 @@ async function runSeed(input) {
|
|
|
8783
8926
|
skipped.push(relPath);
|
|
8784
8927
|
} catch {
|
|
8785
8928
|
await mkdir11(join34(absPath, ".."), { recursive: true });
|
|
8786
|
-
await
|
|
8929
|
+
await writeFile8(absPath, content, "utf8");
|
|
8787
8930
|
created.push(relPath);
|
|
8788
8931
|
}
|
|
8789
8932
|
}
|
|
@@ -8793,7 +8936,7 @@ async function runSeed(input) {
|
|
|
8793
8936
|
skipped.push("raw/articles/example-source.md");
|
|
8794
8937
|
} catch {
|
|
8795
8938
|
await mkdir11(join34(rawPath, ".."), { recursive: true });
|
|
8796
|
-
await
|
|
8939
|
+
await writeFile8(rawPath, EXAMPLE_RAW, "utf8");
|
|
8797
8940
|
created.push("raw/articles/example-source.md");
|
|
8798
8941
|
}
|
|
8799
8942
|
if (created.length > 0) {
|
|
@@ -8815,7 +8958,7 @@ async function runSeed(input) {
|
|
|
8815
8958
|
}
|
|
8816
8959
|
|
|
8817
8960
|
// src/commands/canvas.ts
|
|
8818
|
-
import { readFile as readFile17, writeFile as
|
|
8961
|
+
import { readFile as readFile17, writeFile as writeFile9 } from "fs/promises";
|
|
8819
8962
|
import { existsSync as existsSync12 } from "fs";
|
|
8820
8963
|
import { join as join35 } from "path";
|
|
8821
8964
|
var NODE_WIDTH = 240;
|
|
@@ -8935,7 +9078,7 @@ async function runCanvasGenerate(input) {
|
|
|
8935
9078
|
const canvas = { nodes, edges };
|
|
8936
9079
|
const outPath = join35(input.vault, "vault-graph.canvas");
|
|
8937
9080
|
try {
|
|
8938
|
-
await
|
|
9081
|
+
await writeFile9(outPath, JSON.stringify(canvas, null, 2));
|
|
8939
9082
|
} catch (e) {
|
|
8940
9083
|
return {
|
|
8941
9084
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -8954,11 +9097,252 @@ written: ${outPath}`
|
|
|
8954
9097
|
};
|
|
8955
9098
|
}
|
|
8956
9099
|
|
|
9100
|
+
// src/commands/source-disposition.ts
|
|
9101
|
+
async function runSourceDisposition(input) {
|
|
9102
|
+
if (!input.write) {
|
|
9103
|
+
const plan = await planSourceDisposition(input);
|
|
9104
|
+
return { exitCode: plan.ok ? ExitCode.OK : ExitCode.USAGE, result: plan };
|
|
9105
|
+
}
|
|
9106
|
+
if (!input.approve) return { exitCode: ExitCode.USAGE, result: { ok: false, error: "APPROVAL_INVALID", detail: { message: "--write requires --approve" } } };
|
|
9107
|
+
const applied = await applySourceDisposition({ ...input, approve: input.approve });
|
|
9108
|
+
if (!applied.ok) return { exitCode: ExitCode.USAGE, result: applied };
|
|
9109
|
+
return { exitCode: ExitCode.OK, result: ok({ ...applied.data, humanHint: `${input.status} recorded for ${input.rawPath}` }) };
|
|
9110
|
+
}
|
|
9111
|
+
|
|
9112
|
+
// src/utils/source-disposal.ts
|
|
9113
|
+
import { createHash as createHash6 } from "crypto";
|
|
9114
|
+
import { readFile as readFile19, unlink as unlink5 } from "fs/promises";
|
|
9115
|
+
|
|
9116
|
+
// src/utils/raw-asset-reference-index.ts
|
|
9117
|
+
import { readdir as readdir7, readFile as readFile18 } from "fs/promises";
|
|
9118
|
+
import { join as join36, relative as relative4, sep as sep3 } from "path";
|
|
9119
|
+
var PREVIEW_EXTENSIONS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "webp", "svg", "bmp", "avif", "pdf", "mp3", "wav", "m4a", "mp4", "webm"]);
|
|
9120
|
+
async function walkFiles(root, dir = root) {
|
|
9121
|
+
let entries;
|
|
9122
|
+
try {
|
|
9123
|
+
entries = await readdir7(dir, { withFileTypes: true });
|
|
9124
|
+
} catch {
|
|
9125
|
+
return [];
|
|
9126
|
+
}
|
|
9127
|
+
const out = [];
|
|
9128
|
+
for (const entry of entries) {
|
|
9129
|
+
const path = join36(dir, entry.name);
|
|
9130
|
+
if (entry.isDirectory()) out.push(...await walkFiles(root, path));
|
|
9131
|
+
else if (entry.isFile()) out.push(relative4(root, path).split(sep3).join("/"));
|
|
9132
|
+
}
|
|
9133
|
+
return out;
|
|
9134
|
+
}
|
|
9135
|
+
function supportedPreview(path) {
|
|
9136
|
+
const ext = path.includes(".") ? path.slice(path.lastIndexOf(".") + 1).toLowerCase() : "";
|
|
9137
|
+
return PREVIEW_EXTENSIONS.has(ext);
|
|
9138
|
+
}
|
|
9139
|
+
function addInbound(map, target, source) {
|
|
9140
|
+
const refs = map.get(target) ?? /* @__PURE__ */ new Set();
|
|
9141
|
+
refs.add(source);
|
|
9142
|
+
map.set(target, refs);
|
|
9143
|
+
}
|
|
9144
|
+
async function buildRawAssetReferenceIndex(vault) {
|
|
9145
|
+
const assetRoot = join36(vault, "raw", "assets");
|
|
9146
|
+
const relativeAssets = await walkFiles(assetRoot);
|
|
9147
|
+
const assets = relativeAssets.map((path) => `raw/assets/${path}`).sort((a, b) => a.localeCompare(b));
|
|
9148
|
+
const assetSet = new Set(assets);
|
|
9149
|
+
const byBasename = /* @__PURE__ */ new Map();
|
|
9150
|
+
for (const asset of assets) {
|
|
9151
|
+
const name = asset.slice(asset.lastIndexOf("/") + 1);
|
|
9152
|
+
byBasename.set(name, [...byBasename.get(name) ?? [], asset]);
|
|
9153
|
+
}
|
|
9154
|
+
const scan = await scanVault(vault);
|
|
9155
|
+
if (!scan.ok) return { assets, inbound: /* @__PURE__ */ new Map(), references: [] };
|
|
9156
|
+
const inboundSets = /* @__PURE__ */ new Map();
|
|
9157
|
+
const references = [];
|
|
9158
|
+
for (const page of scan.data.allMarkdown) {
|
|
9159
|
+
let text;
|
|
9160
|
+
try {
|
|
9161
|
+
text = await readFile18(page.absPath, "utf8");
|
|
9162
|
+
} catch {
|
|
9163
|
+
continue;
|
|
9164
|
+
}
|
|
9165
|
+
for (const match of text.matchAll(/!\[\[([^\]|#]+)(?:[|#][^\]]*)?\]\]/g)) {
|
|
9166
|
+
const target = match[1].trim();
|
|
9167
|
+
if (target.startsWith("raw/assets/")) {
|
|
9168
|
+
const exists = assetSet.has(target);
|
|
9169
|
+
references.push({ source_path: page.relPath, target, kind: "obsidian-embed", exists, preview_supported: supportedPreview(target) });
|
|
9170
|
+
if (exists) addInbound(inboundSets, target, page.relPath);
|
|
9171
|
+
} else if (!target.includes("/")) {
|
|
9172
|
+
const candidates = (byBasename.get(target) ?? []).sort((a, b) => a.localeCompare(b));
|
|
9173
|
+
if (candidates.length > 0) {
|
|
9174
|
+
references.push({ source_path: page.relPath, target, kind: "ambiguous-embed", exists: candidates.length === 1, preview_supported: supportedPreview(target), candidates });
|
|
9175
|
+
if (candidates.length === 1) addInbound(inboundSets, candidates[0], page.relPath);
|
|
9176
|
+
}
|
|
9177
|
+
}
|
|
9178
|
+
}
|
|
9179
|
+
for (const match of text.matchAll(/!\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g)) {
|
|
9180
|
+
const target = match[1].trim().replace(/^<|>$/g, "");
|
|
9181
|
+
if (/^https?:\/\//i.test(target)) {
|
|
9182
|
+
references.push({ source_path: page.relPath, target, kind: "external-image", exists: true, preview_supported: supportedPreview(new URL(target).pathname) });
|
|
9183
|
+
} else if (target.startsWith("raw/assets/")) {
|
|
9184
|
+
const exists = assetSet.has(target);
|
|
9185
|
+
references.push({ source_path: page.relPath, target, kind: "markdown-image", exists, preview_supported: supportedPreview(target) });
|
|
9186
|
+
if (exists) addInbound(inboundSets, target, page.relPath);
|
|
9187
|
+
}
|
|
9188
|
+
}
|
|
9189
|
+
}
|
|
9190
|
+
references.sort((a, b) => a.target.localeCompare(b.target) || a.source_path.localeCompare(b.source_path));
|
|
9191
|
+
const inbound = new Map([...inboundSets.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([target, refs]) => [target, [...refs].sort()]));
|
|
9192
|
+
return { assets, inbound, references };
|
|
9193
|
+
}
|
|
9194
|
+
|
|
9195
|
+
// src/utils/source-disposal.ts
|
|
9196
|
+
function sha256(bytes) {
|
|
9197
|
+
return createHash6("sha256").update(bytes).digest("hex");
|
|
9198
|
+
}
|
|
9199
|
+
function exactTarget(path) {
|
|
9200
|
+
if (/[?*\[\]{}]/.test(path) || !path.includes("/") || path.endsWith("/")) return err("RAW_DISPOSAL_TARGET_INVALID", { path, message: "exact file path required; globs, basenames, and directories are refused" });
|
|
9201
|
+
const classified = classifyRawPath(path);
|
|
9202
|
+
if (!classified.ok) return classified;
|
|
9203
|
+
return ok(true);
|
|
9204
|
+
}
|
|
9205
|
+
async function planSourceDisposal(input) {
|
|
9206
|
+
const exact = exactTarget(input.rawPath);
|
|
9207
|
+
if (!exact.ok) return exact;
|
|
9208
|
+
const authorized = authorizeRawOperation({
|
|
9209
|
+
operationClass: "destructive-remove",
|
|
9210
|
+
trigger: "attended-apply",
|
|
9211
|
+
source: input.rawPath,
|
|
9212
|
+
explicitExactTarget: true
|
|
9213
|
+
});
|
|
9214
|
+
if (!authorized.ok) return authorized;
|
|
9215
|
+
const reason = input.reason.normalize("NFC").trim();
|
|
9216
|
+
if (!reason) return err("RAW_DISPOSAL_TARGET_INVALID", { field: "reason" });
|
|
9217
|
+
const sensitive = scanSensitiveContent(reason);
|
|
9218
|
+
if (sensitive.length > 0) return err("SENSITIVE_CONTENT_DETECTED", { findings: sensitive });
|
|
9219
|
+
const target = await resolveExistingRegularFileInsideVault(input.vault, input.rawPath);
|
|
9220
|
+
if (!target.ok) return err("RAW_DISPOSAL_TARGET_INVALID", { path: input.rawPath, cause: target });
|
|
9221
|
+
const bytes = await readFile19(target.data);
|
|
9222
|
+
const size = bytes.byteLength;
|
|
9223
|
+
let typedReferences = [];
|
|
9224
|
+
let otherReferences = [];
|
|
9225
|
+
let assetInboundReferences = [];
|
|
9226
|
+
if (input.rawPath.startsWith("raw/assets/")) {
|
|
9227
|
+
const assets = await buildRawAssetReferenceIndex(input.vault);
|
|
9228
|
+
assetInboundReferences = assets.inbound.get(input.rawPath) ?? [];
|
|
9229
|
+
} else if (input.rawPath.endsWith(".md")) {
|
|
9230
|
+
const scan = await scanVault(input.vault);
|
|
9231
|
+
if (!scan.ok) return scan;
|
|
9232
|
+
const typedPaths = new Set(scan.data.typedKnowledge.map((page) => page.relPath));
|
|
9233
|
+
const relocations = await readSourceRelocations(input.vault);
|
|
9234
|
+
if (!relocations.ok) return relocations;
|
|
9235
|
+
const refs = await buildSourceReferenceIndex({
|
|
9236
|
+
typedPages: scan.data.typedKnowledge,
|
|
9237
|
+
otherPages: scan.data.allMarkdown.filter((page) => !typedPaths.has(page.relPath) && !page.relPath.startsWith("raw/")),
|
|
9238
|
+
availableRawPaths: scan.data.raw.map((page) => page.relPath),
|
|
9239
|
+
relocationProjection: buildSourceRelocationProjection(relocations.data)
|
|
9240
|
+
});
|
|
9241
|
+
typedReferences = refs.integratedBy.get(input.rawPath) ?? [];
|
|
9242
|
+
otherReferences = refs.referencedElsewhereBy.get(input.rawPath) ?? [];
|
|
9243
|
+
}
|
|
9244
|
+
const completeHash = sha256(bytes);
|
|
9245
|
+
const occurredAt = input.now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
9246
|
+
const deleteIntentPath = `meta/delete-intents/${pathToIntentFilename(input.rawPath)}`;
|
|
9247
|
+
const operation_id = operationId("source-disposal", [input.rawPath, completeHash, reason, ...typedReferences, ...otherReferences, ...assetInboundReferences]);
|
|
9248
|
+
const base = {
|
|
9249
|
+
raw_path: input.rawPath,
|
|
9250
|
+
complete_sha256: completeHash,
|
|
9251
|
+
size_bytes: size,
|
|
9252
|
+
typed_references: typedReferences,
|
|
9253
|
+
other_references: otherReferences,
|
|
9254
|
+
asset_inbound_references: assetInboundReferences,
|
|
9255
|
+
delete_intent_path: deleteIntentPath,
|
|
9256
|
+
recoverability: "Recoverable only from Git/S3 history or another backup after deletion; the delete intent prevents automatic resurrection.",
|
|
9257
|
+
reason,
|
|
9258
|
+
operation_id,
|
|
9259
|
+
occurred_at: occurredAt
|
|
9260
|
+
};
|
|
9261
|
+
return ok({ ...base, approval_token: encodeSourceActionApproval({ contract: "source-disposal/v1", ...base }), write: false });
|
|
9262
|
+
}
|
|
9263
|
+
async function applySourceDisposal(input) {
|
|
9264
|
+
if (!input.attended) return err("RAW_DISPOSAL_ATTENDED_REQUIRED", { message: "permanent disposal is refused in scheduled/headless sessions" });
|
|
9265
|
+
const authorized = authorizeRawOperation({
|
|
9266
|
+
operationClass: "destructive-remove",
|
|
9267
|
+
trigger: "attended-apply",
|
|
9268
|
+
source: input.rawPath,
|
|
9269
|
+
explicitExactTarget: true
|
|
9270
|
+
});
|
|
9271
|
+
if (!authorized.ok) return authorized;
|
|
9272
|
+
const decoded = decodeSourceActionApproval(input.approve);
|
|
9273
|
+
if (!decoded.ok) return decoded;
|
|
9274
|
+
const occurredAt = typeof decoded.data.occurred_at === "string" ? decoded.data.occurred_at : void 0;
|
|
9275
|
+
const planned = await planSourceDisposal({ vault: input.vault, rawPath: input.rawPath, reason: input.reason, now: occurredAt });
|
|
9276
|
+
if (!planned.ok) return planned;
|
|
9277
|
+
if (planned.data.approval_token !== input.approve) return err("APPROVAL_INVALID", { message: "source disposal approval does not match live target or references" });
|
|
9278
|
+
const event = await writeLogEvent(input.vault, {
|
|
9279
|
+
schema: "skillwiki-log-event/v1",
|
|
9280
|
+
operation_id: planned.data.operation_id,
|
|
9281
|
+
occurred_at: planned.data.occurred_at,
|
|
9282
|
+
host_id: input.hostId ?? "local",
|
|
9283
|
+
actor: input.actor ?? "skillwiki-cli",
|
|
9284
|
+
kind: "source-disposal-approved",
|
|
9285
|
+
target: input.rawPath,
|
|
9286
|
+
note: input.reason,
|
|
9287
|
+
metadata: {
|
|
9288
|
+
raw_path: input.rawPath,
|
|
9289
|
+
complete_sha256: planned.data.complete_sha256,
|
|
9290
|
+
typed_references: planned.data.typed_references,
|
|
9291
|
+
other_references: planned.data.other_references,
|
|
9292
|
+
asset_inbound_references: planned.data.asset_inbound_references,
|
|
9293
|
+
delete_intent_path: planned.data.delete_intent_path
|
|
9294
|
+
}
|
|
9295
|
+
});
|
|
9296
|
+
if (!event.ok) return event;
|
|
9297
|
+
const tombstonePath = await writeDeleteIntent(input.vault, buildDeleteIntent({
|
|
9298
|
+
path: input.rawPath,
|
|
9299
|
+
action: "remove",
|
|
9300
|
+
actor: input.actor ?? "skillwiki-cli",
|
|
9301
|
+
source: "cli",
|
|
9302
|
+
reason: input.reason,
|
|
9303
|
+
created: planned.data.occurred_at
|
|
9304
|
+
}));
|
|
9305
|
+
const target = await resolveExistingRegularFileInsideVault(input.vault, input.rawPath);
|
|
9306
|
+
if (!target.ok) return err("APPROVAL_INVALID", { message: "source disposal target is no longer a safe exact file", cause: target });
|
|
9307
|
+
await unlink5(target.data);
|
|
9308
|
+
const completedOperationId = operationId("source-disposal-completed", [planned.data.operation_id]);
|
|
9309
|
+
const completed = await writeLogEvent(input.vault, {
|
|
9310
|
+
schema: "skillwiki-log-event/v1",
|
|
9311
|
+
operation_id: completedOperationId,
|
|
9312
|
+
occurred_at: planned.data.occurred_at,
|
|
9313
|
+
host_id: input.hostId ?? "local",
|
|
9314
|
+
actor: input.actor ?? "skillwiki-cli",
|
|
9315
|
+
kind: "source-disposal-completed",
|
|
9316
|
+
target: input.rawPath,
|
|
9317
|
+
note: input.reason,
|
|
9318
|
+
metadata: {
|
|
9319
|
+
approval_operation_id: planned.data.operation_id,
|
|
9320
|
+
raw_path: input.rawPath,
|
|
9321
|
+
complete_sha256: planned.data.complete_sha256,
|
|
9322
|
+
delete_intent_path: planned.data.delete_intent_path
|
|
9323
|
+
}
|
|
9324
|
+
});
|
|
9325
|
+
if (!completed.ok) return completed;
|
|
9326
|
+
return ok({ removed: input.rawPath, tombstone_path: tombstonePath, event_path: completed.data.path, operation_id: planned.data.operation_id });
|
|
9327
|
+
}
|
|
9328
|
+
|
|
9329
|
+
// src/commands/source-disposal.ts
|
|
9330
|
+
async function runSourceDisposal(input) {
|
|
9331
|
+
if (!input.write) {
|
|
9332
|
+
const plan = await planSourceDisposal(input);
|
|
9333
|
+
return { exitCode: plan.ok ? ExitCode.OK : ExitCode.USAGE, result: plan };
|
|
9334
|
+
}
|
|
9335
|
+
if (!input.approve) return { exitCode: ExitCode.USAGE, result: { ok: false, error: "APPROVAL_INVALID", detail: { message: "--write requires --approve" } } };
|
|
9336
|
+
const applied = await applySourceDisposal({ ...input, approve: input.approve, attended: input.attended === true });
|
|
9337
|
+
if (!applied.ok) return { exitCode: ExitCode.USAGE, result: applied };
|
|
9338
|
+
return { exitCode: ExitCode.OK, result: ok({ ...applied.data, humanHint: `permanently disposed ${input.rawPath}; ${applied.data.tombstone_path}` }) };
|
|
9339
|
+
}
|
|
9340
|
+
|
|
8957
9341
|
// src/commands/fleet-health.ts
|
|
8958
9342
|
import { existsSync as existsSync13, readFileSync as readFileSync16 } from "fs";
|
|
8959
9343
|
import { execSync as nodeExecSync } from "child_process";
|
|
8960
9344
|
import { hostname as nodeHostname, platform as nodePlatform } from "os";
|
|
8961
|
-
import { join as
|
|
9345
|
+
import { join as join37 } from "path";
|
|
8962
9346
|
var SSH_TIMEOUT_MS = 15e3;
|
|
8963
9347
|
var TIMER_UNIT = "agent-memory-trends.timer";
|
|
8964
9348
|
var SERVICE_UNIT = "agent-memory-trends.service";
|
|
@@ -9188,7 +9572,7 @@ async function runFleetHealth(input) {
|
|
|
9188
9572
|
const home = input.home ?? env.HOME ?? "";
|
|
9189
9573
|
const osHostname = input.osHostname ?? env.HOSTNAME ?? nodeHostname();
|
|
9190
9574
|
const vault = input.vault ?? env.WIKI_PATH;
|
|
9191
|
-
const file = input.file ?? (vault ?
|
|
9575
|
+
const file = input.file ?? (vault ? join37(vault, FLEET_REL_PATH) : void 0);
|
|
9192
9576
|
if (!file) {
|
|
9193
9577
|
return {
|
|
9194
9578
|
exitCode: ExitCode.NO_VAULT_CONFIGURED,
|
|
@@ -9237,7 +9621,7 @@ async function runFleetHealth(input) {
|
|
|
9237
9621
|
healthy
|
|
9238
9622
|
});
|
|
9239
9623
|
} else {
|
|
9240
|
-
if (!hasDeclaredSshAccess(t.host, localHostId, t.sshAlias)) {
|
|
9624
|
+
if (!localHostId || !hasDeclaredSshAccess(t.host, localHostId, t.sshAlias)) {
|
|
9241
9625
|
rows.push({
|
|
9242
9626
|
host: t.id,
|
|
9243
9627
|
timer: "unknown",
|
|
@@ -9273,14 +9657,14 @@ async function runFleetHealth(input) {
|
|
|
9273
9657
|
|
|
9274
9658
|
// src/utils/auto-commit.ts
|
|
9275
9659
|
import { existsSync as existsSync14 } from "fs";
|
|
9276
|
-
import { join as
|
|
9660
|
+
import { join as join38 } from "path";
|
|
9277
9661
|
async function postCommit(vault, exitCode) {
|
|
9278
9662
|
if (exitCode !== 0) return;
|
|
9279
9663
|
const home = process.env.HOME ?? "";
|
|
9280
9664
|
const dotenv = await parseDotenvFile(configPath(home));
|
|
9281
9665
|
const autoCommit = process.env.AUTO_COMMIT ?? dotenv["AUTO_COMMIT"];
|
|
9282
9666
|
if (autoCommit === "false") return;
|
|
9283
|
-
if (!existsSync14(
|
|
9667
|
+
if (!existsSync14(join38(vault, ".git"))) return;
|
|
9284
9668
|
const lastOps = readLastOp(vault);
|
|
9285
9669
|
if (lastOps.length === 0) return;
|
|
9286
9670
|
const porcelain = git(vault, ["status", "--porcelain", "--", ...VAULT_COMMIT_PATHSPEC]);
|
|
@@ -9464,7 +9848,7 @@ program.command("validate <file>").description("validate vault page frontmatter
|
|
|
9464
9848
|
emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
|
|
9465
9849
|
});
|
|
9466
9850
|
program.command("graph").description("graph subcommands").command("build <vault>").option("--out <path>", "graph output path (default: <vault>/.skillwiki/graph.json)").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
9467
|
-
const out = opts.out ??
|
|
9851
|
+
const out = opts.out ?? join39(vault, ".skillwiki", "graph.json");
|
|
9468
9852
|
return emitGuardedVaultWrite(vault, "graph build", () => runGraphBuild({ vault, out }));
|
|
9469
9853
|
});
|
|
9470
9854
|
var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");
|
|
@@ -9478,10 +9862,76 @@ program.command("overlap [vault]").description("detect typed-knowledge pages tha
|
|
|
9478
9862
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
9479
9863
|
else emit(await runOverlap({ vault: v.vault }), v.vault);
|
|
9480
9864
|
});
|
|
9481
|
-
program.command("query <text> [vault]").description("score and rank vault pages by relevance to a query").option("--limit <n>", "max results to return", (s) => parseInt(s, 10), 10).option("--wiki <name>", "wiki profile name").action(async (text, vault, opts) => {
|
|
9865
|
+
program.command("query <text> [vault]").description("score and rank vault pages by relevance to a query").option("--limit <n>", "max results to return", (s) => parseInt(s, 10), 10).option("--include-pending", "include a separate pending-evidence channel").option("--wiki <name>", "wiki profile name").action(async (text, vault, opts) => {
|
|
9482
9866
|
const v = await resolveVaultArg(vault, opts.wiki);
|
|
9483
9867
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
9484
|
-
else emit(await runQuery({ text, vault: v.vault, limit: opts.limit }), v.vault);
|
|
9868
|
+
else emit(await runQuery({ text, vault: v.vault, limit: opts.limit, includePending: opts.includePending }), v.vault, { postCommit: false });
|
|
9869
|
+
});
|
|
9870
|
+
var sourcesCmd = program.command("sources").description("inspect raw-source lifecycle state");
|
|
9871
|
+
sourcesCmd.command("pending [vault]").description("list captured articles and papers awaiting typed integration").option("--since <date>", "include sources captured on or after YYYY-MM-DD").option("--older-than <days>", "include sources at least this many days old", (s) => parseInt(s, 10)).option("--match <text>", "literal title, URL, or path match").option("--ingested-by <channel>", "manual, wiki-ingest, proj-work, or unknown").option("--scope <scope>", "articles, papers, or all", "all").option("--sort <order>", "newest or oldest", "newest").option("--limit <n>", "max items to return", (s) => parseInt(s, 10), 50).option("--all", "return the complete matching inventory").option("--include-integrated", "also return actively integrated sources").option("--include-archived", "also return raw/archived sources").option("--include-duplicates", "also return raw/duplicates sources").option("--include-legacy-archived", "also return legacy _archive/raw sources").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
9872
|
+
const v = await resolveVaultArg(vault, opts.wiki);
|
|
9873
|
+
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
9874
|
+
else emit(await runSourcesPending({
|
|
9875
|
+
vault: v.vault,
|
|
9876
|
+
since: opts.since,
|
|
9877
|
+
olderThan: opts.olderThan,
|
|
9878
|
+
match: opts.match,
|
|
9879
|
+
ingestedBy: opts.ingestedBy,
|
|
9880
|
+
scope: opts.scope,
|
|
9881
|
+
sort: opts.sort,
|
|
9882
|
+
limit: opts.limit,
|
|
9883
|
+
all: opts.all,
|
|
9884
|
+
includeIntegrated: opts.includeIntegrated,
|
|
9885
|
+
includeArchived: opts.includeArchived,
|
|
9886
|
+
includeDuplicates: opts.includeDuplicates,
|
|
9887
|
+
includeLegacyArchived: opts.includeLegacyArchived
|
|
9888
|
+
}), v.vault, { postCommit: false });
|
|
9889
|
+
});
|
|
9890
|
+
sourcesCmd.command("disposition <raw-path> [vault]").description("record an append-only editorial decision for an exact raw source").requiredOption("--status <status>", "reviewed-no-op, deferred, duplicate, out-of-scope, superseded, or reopened").requiredOption("--reason <text>", "editorial reason").option("--review-after <date>", "future review date for deferred").option("--duplicate-of <raw-path>", "canonical exact raw path for duplicate").option("--write", "write the approved disposition event").option("--approve <token>", "approve the exact state-bound disposition plan").option("--wiki <name>", "wiki profile name").action(async (rawPath, vault, opts) => {
|
|
9891
|
+
const v = await resolveVaultArg(vault, opts.wiki);
|
|
9892
|
+
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
9893
|
+
else if (!opts.write) emit(await runSourceDisposition({
|
|
9894
|
+
vault: v.vault,
|
|
9895
|
+
rawPath,
|
|
9896
|
+
status: opts.status,
|
|
9897
|
+
reason: opts.reason,
|
|
9898
|
+
reviewAfter: opts.reviewAfter,
|
|
9899
|
+
duplicateOf: opts.duplicateOf,
|
|
9900
|
+
write: false
|
|
9901
|
+
}), v.vault, { postCommit: false });
|
|
9902
|
+
else return emitManagedVaultWrite(
|
|
9903
|
+
v.vault,
|
|
9904
|
+
"sources disposition",
|
|
9905
|
+
(receipt) => runSourceDisposition({
|
|
9906
|
+
vault: v.vault,
|
|
9907
|
+
rawPath,
|
|
9908
|
+
status: opts.status,
|
|
9909
|
+
reason: opts.reason,
|
|
9910
|
+
reviewAfter: opts.reviewAfter,
|
|
9911
|
+
duplicateOf: opts.duplicateOf,
|
|
9912
|
+
write: true,
|
|
9913
|
+
approve: opts.approve
|
|
9914
|
+
}),
|
|
9915
|
+
{ allowImmutableRecord: true }
|
|
9916
|
+
);
|
|
9917
|
+
});
|
|
9918
|
+
sourcesCmd.command("dispose <raw-path> [vault]").description("permanently delete one exact raw object after an attended approved preview").requiredOption("--reason <text>", "permanent-disposal reason").option("--write", "apply the approved exact-target disposal").option("--approve <token>", "approve the exact state-bound disposal plan").option("--wiki <name>", "wiki profile name").action(async (rawPath, vault, opts) => {
|
|
9919
|
+
const v = await resolveVaultArg(vault, opts.wiki);
|
|
9920
|
+
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
9921
|
+
else if (!opts.write) emit(await runSourceDisposal({ vault: v.vault, rawPath, reason: opts.reason, write: false }), v.vault, { postCommit: false });
|
|
9922
|
+
else return emitManagedVaultWrite(
|
|
9923
|
+
v.vault,
|
|
9924
|
+
"sources dispose",
|
|
9925
|
+
() => runSourceDisposal({
|
|
9926
|
+
vault: v.vault,
|
|
9927
|
+
rawPath,
|
|
9928
|
+
reason: opts.reason,
|
|
9929
|
+
write: true,
|
|
9930
|
+
approve: opts.approve,
|
|
9931
|
+
attended: process.stdin.isTTY === true && process.stdout.isTTY === true
|
|
9932
|
+
}),
|
|
9933
|
+
{ allowImmutableRecord: true }
|
|
9934
|
+
);
|
|
9485
9935
|
});
|
|
9486
9936
|
program.command("orphans [vault]").description("find pages not referenced by any other page").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => emit(await runOrphans({
|
|
9487
9937
|
vault,
|
|
@@ -9647,15 +10097,15 @@ program.command("topic-map-check [vault]").description("check whether a topic ma
|
|
|
9647
10097
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
9648
10098
|
else emit(await runTopicMapCheck({ vault: v.vault, threshold: opts.threshold }), v.vault);
|
|
9649
10099
|
});
|
|
9650
|
-
program.command("stale [vault]").description("identify stale transcripts and incomplete work items").option("--archive", "
|
|
10100
|
+
program.command("stale [vault]").description("identify stale transcripts and incomplete work items").option("--archive", "preview lifecycle-first archives for stale items", false).option("--apply", "apply an approved stale archive preview", false).option("--approve <token>", "approve the exact state-bound stale archive plan").option("--days <n>", "staleness threshold in days", (s) => parseInt(s, 10), 3).option("--force-scan", "infer kind/project from filename and content when frontmatter is missing", false).option("--project <slug>", "scope to a single project").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
9651
10101
|
const v = await resolveVaultArg(vault, opts.wiki);
|
|
9652
10102
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
9653
|
-
else if (opts.archive) return
|
|
10103
|
+
else if (opts.archive && opts.apply) return emitManagedVaultWrite(
|
|
9654
10104
|
v.vault,
|
|
9655
10105
|
"stale --archive",
|
|
9656
|
-
() => runStale({ vault: v.vault, days: opts.days, archive: true, forceScan: !!opts.forceScan, project: opts.project })
|
|
10106
|
+
() => runStale({ vault: v.vault, days: opts.days, archive: true, apply: true, approve: opts.approve, forceScan: !!opts.forceScan, project: opts.project })
|
|
9657
10107
|
);
|
|
9658
|
-
else emit(await runStale({ vault: v.vault, days: opts.days, archive: false, forceScan: !!opts.forceScan, project: opts.project }), v.vault);
|
|
10108
|
+
else emit(await runStale({ vault: v.vault, days: opts.days, archive: !!opts.archive, apply: false, forceScan: !!opts.forceScan, project: opts.project }), v.vault, { postCommit: false });
|
|
9659
10109
|
});
|
|
9660
10110
|
program.command("claim <transcript> [vault]").description("claim an unclaimed transcript by creating a work item with source: link").option("--project <slug>", "project slug (overrides transcript frontmatter)").option("--slug <slug>", "work-item slug (defaults to transcript filename without date/kind prefix)").option("--wiki <name>", "wiki profile name").action(async (transcript, vault, opts) => {
|
|
9661
10111
|
const v = await resolveVaultArg(vault, opts.wiki);
|
|
@@ -9786,6 +10236,21 @@ projectionsCmd.command("repair-legacy [vault]").description("repair one supporte
|
|
|
9786
10236
|
program.command("lint [vault]").description("run all vault health checks").option("--days <n>", "stale threshold", (s) => parseInt(s, 10), 90).option("--lines <n>", "pagesize threshold", (s) => parseInt(s, 10), 200).option("--log-threshold <n>", "log rotation threshold", (s) => parseInt(s, 10), 500).option("--fix", "auto-fix supported lint violations").option("--only <bucket>", "run only the specified lint bucket").option("--summary", "emit bounded bucket counts instead of full item arrays", false).option("--examples <n>", "example count per bucket in summary mode", (s) => parseInt(s, 10), 3).option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
9787
10237
|
const v = await resolveVaultArg(vault, opts.wiki);
|
|
9788
10238
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
10239
|
+
else if (opts.fix && opts.summary) return emitGuardedVaultWrite(
|
|
10240
|
+
v.vault,
|
|
10241
|
+
"lint --fix",
|
|
10242
|
+
() => runLint({
|
|
10243
|
+
vault: v.vault,
|
|
10244
|
+
source: vault ? "flag" : void 0,
|
|
10245
|
+
days: opts.days,
|
|
10246
|
+
lines: opts.lines,
|
|
10247
|
+
logThreshold: opts.logThreshold,
|
|
10248
|
+
fix: true,
|
|
10249
|
+
only: opts.only,
|
|
10250
|
+
summary: true,
|
|
10251
|
+
examplesLimit: opts.examples
|
|
10252
|
+
})
|
|
10253
|
+
);
|
|
9789
10254
|
else if (opts.fix) return emitGuardedVaultWrite(
|
|
9790
10255
|
v.vault,
|
|
9791
10256
|
"lint --fix",
|
|
@@ -9797,8 +10262,7 @@ program.command("lint [vault]").description("run all vault health checks").optio
|
|
|
9797
10262
|
logThreshold: opts.logThreshold,
|
|
9798
10263
|
fix: true,
|
|
9799
10264
|
only: opts.only,
|
|
9800
|
-
summary:
|
|
9801
|
-
examplesLimit: opts.summary ? opts.examples : void 0
|
|
10265
|
+
summary: false
|
|
9802
10266
|
})
|
|
9803
10267
|
);
|
|
9804
10268
|
else if (opts.summary) emit(await runLint({
|
|
@@ -9865,10 +10329,10 @@ program.command("status [vault]").description("output vault diagnostics").option
|
|
|
9865
10329
|
langEnvValue: process.env.WIKI_LANG
|
|
9866
10330
|
}), v.vault);
|
|
9867
10331
|
});
|
|
9868
|
-
program.command("archive <page> [vault]").description("archive a typed-knowledge or raw page").option("--wiki <name>", "wiki profile name").option("--cascade", "scan vault for references (wikilinks + sources arrays); preview by default", false).option("--apply", "with --cascade: mutate sources arrays and archive (without --apply, --cascade is preview-only)", false).option("--remote <remote>", "rclone remote root to prune the archived source path, for example seaweed-wiki:cloud/wiki").option("--remote-delete", "delete the archived source path from the remote after local archive", false).option("--max-remote-deletes <n>", "maximum remote object deletes allowed", "1").action(async (page, vault, opts) => {
|
|
10332
|
+
program.command("archive <page> [vault]").description("archive a typed-knowledge or raw page").option("--wiki <name>", "wiki profile name").option("--cascade", "scan vault for references (wikilinks + sources arrays); preview by default", false).option("--apply", "with --cascade: mutate sources arrays and archive (without --apply, --cascade is preview-only)", false).option("--approve <token>", "approve an exact state-bound raw archive plan").option("--remote <remote>", "rclone remote root to prune the archived source path, for example seaweed-wiki:cloud/wiki").option("--remote-delete", "delete the archived source path from the remote after local archive", false).option("--max-remote-deletes <n>", "maximum remote object deletes allowed", "1").action(async (page, vault, opts) => {
|
|
9869
10333
|
const v = await resolveVaultArg(vault, opts.wiki);
|
|
9870
10334
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
9871
|
-
else if (opts.
|
|
10335
|
+
else if (!opts.apply && (opts.cascade || String(page).startsWith("raw/"))) emit(await runArchive({
|
|
9872
10336
|
vault: v.vault,
|
|
9873
10337
|
page,
|
|
9874
10338
|
cascade: true,
|
|
@@ -9885,6 +10349,7 @@ program.command("archive <page> [vault]").description("archive a typed-knowledge
|
|
|
9885
10349
|
page,
|
|
9886
10350
|
cascade: !!opts.cascade,
|
|
9887
10351
|
apply: !!opts.apply,
|
|
10352
|
+
approve: opts.approve,
|
|
9888
10353
|
remote: opts.remote,
|
|
9889
10354
|
remoteDelete: !!opts.remoteDelete,
|
|
9890
10355
|
maxRemoteDeletes: Number.parseInt(opts.maxRemoteDeletes, 10)
|
|
@@ -9917,15 +10382,16 @@ program.command("drift [vault]").description("detect content drift in raw source
|
|
|
9917
10382
|
);
|
|
9918
10383
|
else emit(await runDrift({ vault: v.vault, apply: false, newSince: opts.new }), v.vault);
|
|
9919
10384
|
});
|
|
9920
|
-
program.command("dedup [vault]").description("detect duplicate raw sources by sha256").option("--apply", "rewire citations and
|
|
10385
|
+
program.command("dedup [vault]").description("detect duplicate raw sources by sha256").option("--apply", "rewire citations and preserve duplicate raw files under raw/duplicates", false).option("--approve <token>", "approve the exact state-bound dedup plan").option("--canonical-policy <policy>", "canonical policy: stable-path or scan-order", "stable-path").option("--manifest-out <path>", "write raw dedup delete manifest before applying").option("--manifest-in <path>", "read existing raw dedup delete manifest for remote pruning").option("--remote <remote>", "rclone remote root, for example seaweed-wiki:cloud/wiki").option("--remote-delete", "delete manifest duplicate paths from the remote", false).option("--max-remote-deletes <n>", "maximum remote object deletes allowed", "50").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
9921
10386
|
const v = await resolveVaultArg(vault, opts.wiki);
|
|
9922
10387
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
9923
|
-
else if (opts.apply || opts.manifestOut) return
|
|
10388
|
+
else if (opts.apply || opts.manifestOut) return emitManagedVaultWrite(
|
|
9924
10389
|
v.vault,
|
|
9925
10390
|
opts.apply ? "dedup --apply" : "dedup --manifest-out",
|
|
9926
10391
|
() => runDedup({
|
|
9927
10392
|
vault: v.vault,
|
|
9928
10393
|
apply: opts.apply,
|
|
10394
|
+
approve: opts.approve,
|
|
9929
10395
|
canonicalPolicy: opts.canonicalPolicy,
|
|
9930
10396
|
manifestOut: opts.manifestOut,
|
|
9931
10397
|
manifestIn: opts.manifestIn,
|
|
@@ -9937,6 +10403,7 @@ program.command("dedup [vault]").description("detect duplicate raw sources by sh
|
|
|
9937
10403
|
else emit(await runDedup({
|
|
9938
10404
|
vault: v.vault,
|
|
9939
10405
|
apply: opts.apply,
|
|
10406
|
+
approve: opts.approve,
|
|
9940
10407
|
canonicalPolicy: opts.canonicalPolicy,
|
|
9941
10408
|
manifestOut: opts.manifestOut,
|
|
9942
10409
|
manifestIn: opts.manifestIn,
|