skillwiki 0.9.2 → 0.9.4
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/cli.js +938 -531
- package/package.json +1 -1
- package/skills/.claude-plugin/plugin.json +1 -1
- package/skills/.codex-plugin/plugin.json +1 -1
- package/skills/agents/proj-decide.md +1 -0
- package/skills/agents/proj-distill.md +1 -0
- package/skills/agents/proj-init.md +1 -0
- package/skills/agents/proj-work.md +1 -0
- package/skills/agents/wiki-adapter-prd.md +8 -5
- package/skills/agents/wiki-add-task.md +8 -5
- package/skills/agents/wiki-archive.md +1 -0
- package/skills/agents/wiki-audit.md +1 -0
- package/skills/agents/wiki-canvas.md +1 -0
- package/skills/agents/wiki-crystallize.md +5 -2
- package/skills/agents/wiki-ingest.md +8 -5
- package/skills/agents/wiki-lint.md +5 -3
- package/skills/agents/wiki-query.md +4 -1
- package/skills/agents/wiki-reingest.md +6 -3
- package/skills/agents/wiki-sync.md +1 -0
- package/skills/package.json +1 -1
- package/skills/skills/using-skillwiki/SKILL.md +26 -1
- package/skills/skills/wiki-adapter-prd/SKILL.md +8 -5
- package/skills/skills/wiki-add-task/SKILL.md +9 -6
- package/skills/skills/wiki-crystallize/SKILL.md +5 -2
- package/skills/skills/wiki-ingest/SKILL.md +8 -6
- package/skills/skills/wiki-lint/SKILL.md +5 -3
- package/skills/skills/wiki-query/SKILL.md +4 -1
- package/skills/using-skillwiki/SKILL.md +26 -1
- package/skills/wiki-adapter-prd/SKILL.md +8 -5
- package/skills/wiki-add-task/SKILL.md +9 -6
- package/skills/wiki-crystallize/SKILL.md +5 -2
- package/skills/wiki-ingest/SKILL.md +8 -6
- package/skills/wiki-lint/SKILL.md +5 -3
- package/skills/wiki-query/SKILL.md +4 -1
package/dist/cli.js
CHANGED
|
@@ -69,7 +69,8 @@ var ExitCode = {
|
|
|
69
69
|
BODY_TRUNCATION_GUARD: 47,
|
|
70
70
|
SYNC_LOCK_HELD: 48,
|
|
71
71
|
LOG_APPEND_LOCK_HELD: 49,
|
|
72
|
-
FLEET_MANIFEST_INVALID: 50
|
|
72
|
+
FLEET_MANIFEST_INVALID: 50,
|
|
73
|
+
SENSITIVE_CONTENT_DETECTED: 51
|
|
73
74
|
};
|
|
74
75
|
|
|
75
76
|
// ../shared/src/json-output.ts
|
|
@@ -450,6 +451,148 @@ function sanitizeUrl(u) {
|
|
|
450
451
|
// src/commands/validate.ts
|
|
451
452
|
import { readFile as readFile2, writeFile } from "fs/promises";
|
|
452
453
|
import { join as join2, resolve, relative, sep } from "path";
|
|
454
|
+
|
|
455
|
+
// src/utils/sensitive-content.ts
|
|
456
|
+
import { createHash as createHash2 } from "crypto";
|
|
457
|
+
var REDACTED_RE = /\[REDACTED:[^\]]+\]/i;
|
|
458
|
+
var SYNTHETIC_RE = /^(?:<[^>]+>|\$\{[^}]+\}|REPLACE_WITH_[A-Z0-9_]+|YOUR_[A-Z0-9_]+|EXAMPLE_[A-Z0-9_]+)$/i;
|
|
459
|
+
var MATCHERS = [
|
|
460
|
+
{
|
|
461
|
+
kind: "private_key",
|
|
462
|
+
re: /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
kind: "authorization_header",
|
|
466
|
+
re: /\bAuthorization["']?\s*:\s*["']?(Bearer\s+[A-Za-z0-9._~+/-]{20,})["']?/gi,
|
|
467
|
+
valueGroup: 1
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
kind: "cookie",
|
|
471
|
+
re: /\b(?:Cookie|Set-Cookie)\s*:\s*([^\n]{20,})/gi,
|
|
472
|
+
valueGroup: 1
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
kind: "jwt",
|
|
476
|
+
re: /\b([A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{12,}\.[A-Za-z0-9_-]{12,})\b/g,
|
|
477
|
+
valueGroup: 1
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
kind: "provider_key",
|
|
481
|
+
re: /\b(sk-[A-Za-z0-9_-]{20,}|xox[baprs]-[A-Za-z0-9-]{20,}|gh[pousr]_[A-Za-z0-9_=-]{20,})\b/g,
|
|
482
|
+
valueGroup: 1
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
kind: "access_key",
|
|
486
|
+
re: /\b((?:AKIA|ASIA)[A-Z0-9]{16})\b/g,
|
|
487
|
+
valueGroup: 1
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
kind: "access_key",
|
|
491
|
+
re: /\b(?:access[-_ ]?key|credential)["']?\s*[:=]\s*["']?([A-Za-z0-9._~+/-]{20,})["']?/gi,
|
|
492
|
+
valueGroup: 1
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
kind: "api_key",
|
|
496
|
+
re: /\b(?:api[-_ ]?key)["']?\s*[:=]\s*["']?([A-Za-z0-9._~+/-]{20,})["']?/gi,
|
|
497
|
+
valueGroup: 1
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
kind: "password",
|
|
501
|
+
re: /\b(?:pass(?:word|wd)?)["']?\s*[:=]\s*["']?([^\s`"']{8,})["']?/gi,
|
|
502
|
+
valueGroup: 1
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
kind: "secret",
|
|
506
|
+
re: /\b(?:secret|client[-_ ]?secret)["']?\s*[:=]\s*["']?([A-Za-z0-9._~+/-]{16,})["']?/gi,
|
|
507
|
+
valueGroup: 1
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
kind: "token",
|
|
511
|
+
re: /\b(?:token|session)["']?\s*[:=]\s*["']?([A-Za-z0-9._~+/-]{16,})["']?/gi,
|
|
512
|
+
valueGroup: 1
|
|
513
|
+
}
|
|
514
|
+
];
|
|
515
|
+
function fingerprint(value) {
|
|
516
|
+
return createHash2("sha256").update(value).digest("hex").slice(0, 12);
|
|
517
|
+
}
|
|
518
|
+
function lineFor(text, offset) {
|
|
519
|
+
return text.slice(0, offset).split(/\r?\n/).length;
|
|
520
|
+
}
|
|
521
|
+
function redactMarker(kind, value) {
|
|
522
|
+
return `[REDACTED:${kind}:${fingerprint(value)}]`;
|
|
523
|
+
}
|
|
524
|
+
function isSyntheticPlaceholder(value) {
|
|
525
|
+
return REDACTED_RE.test(value) || SYNTHETIC_RE.test(value.trim());
|
|
526
|
+
}
|
|
527
|
+
function collectMatches(text) {
|
|
528
|
+
const matches = [];
|
|
529
|
+
for (const matcher of MATCHERS) {
|
|
530
|
+
matcher.re.lastIndex = 0;
|
|
531
|
+
for (const m of text.matchAll(matcher.re)) {
|
|
532
|
+
const whole = m[0];
|
|
533
|
+
const start = m.index ?? 0;
|
|
534
|
+
if (REDACTED_RE.test(whole)) continue;
|
|
535
|
+
const value = matcher.valueGroup ? m[matcher.valueGroup] : whole;
|
|
536
|
+
if (isSyntheticPlaceholder(value)) continue;
|
|
537
|
+
const valueOffset = whole.lastIndexOf(value);
|
|
538
|
+
const valueStart = start + Math.max(0, valueOffset);
|
|
539
|
+
matches.push({
|
|
540
|
+
start,
|
|
541
|
+
end: start + whole.length,
|
|
542
|
+
valueStart,
|
|
543
|
+
valueEnd: valueStart + value.length,
|
|
544
|
+
kind: matcher.kind
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return matches.sort((a, b) => {
|
|
549
|
+
if (a.valueStart !== b.valueStart) return a.valueStart - b.valueStart;
|
|
550
|
+
return b.valueEnd - b.valueStart - (a.valueEnd - a.valueStart);
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
function collapseOverlaps(matches) {
|
|
554
|
+
const kept = [];
|
|
555
|
+
for (const match of matches) {
|
|
556
|
+
const overlaps = kept.some((k) => match.valueStart < k.valueEnd && match.valueEnd > k.valueStart);
|
|
557
|
+
if (!overlaps) kept.push(match);
|
|
558
|
+
}
|
|
559
|
+
return kept;
|
|
560
|
+
}
|
|
561
|
+
function scanSensitiveContent(text, opts = {}) {
|
|
562
|
+
return collapseOverlaps(collectMatches(text)).map((match) => {
|
|
563
|
+
const value = text.slice(match.valueStart, match.valueEnd);
|
|
564
|
+
const marker = redactMarker(match.kind, value);
|
|
565
|
+
const rawPreview = text.slice(Math.max(0, match.start - 24), Math.min(text.length, match.end + 24));
|
|
566
|
+
const preview = rawPreview.replace(value, marker);
|
|
567
|
+
return {
|
|
568
|
+
file: opts.file,
|
|
569
|
+
line: lineFor(text, match.valueStart),
|
|
570
|
+
kind: match.kind,
|
|
571
|
+
preview,
|
|
572
|
+
fingerprint: fingerprint(value)
|
|
573
|
+
};
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
function redactSensitiveContent(text, opts = {}) {
|
|
577
|
+
const matches = collapseOverlaps(collectMatches(text));
|
|
578
|
+
if (matches.length === 0) return { text, changed: false, findings: [] };
|
|
579
|
+
let out = "";
|
|
580
|
+
let cursor = 0;
|
|
581
|
+
for (const match of matches) {
|
|
582
|
+
const value = text.slice(match.valueStart, match.valueEnd);
|
|
583
|
+
out += text.slice(cursor, match.valueStart);
|
|
584
|
+
out += redactMarker(match.kind, value);
|
|
585
|
+
cursor = match.valueEnd;
|
|
586
|
+
}
|
|
587
|
+
out += text.slice(cursor);
|
|
588
|
+
return {
|
|
589
|
+
text: out,
|
|
590
|
+
changed: out !== text,
|
|
591
|
+
findings: scanSensitiveContent(text, opts)
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// src/commands/validate.ts
|
|
453
596
|
var TYPE_TO_SECTION = {
|
|
454
597
|
entity: "Entities",
|
|
455
598
|
concept: "Concepts",
|
|
@@ -480,6 +623,26 @@ async function runValidate(input) {
|
|
|
480
623
|
return { exitCode: ExitCode.INVALID_FRONTMATTER, result: fm };
|
|
481
624
|
}
|
|
482
625
|
const det = detectSchema(fm.data);
|
|
626
|
+
const sensitiveFindings = scanSensitiveContent(text, { file: input.file });
|
|
627
|
+
if (sensitiveFindings.length > 0) {
|
|
628
|
+
const errors = sensitiveFindings.map((f) => ({
|
|
629
|
+
path: "sensitive_content",
|
|
630
|
+
message: `${f.kind} at line ${f.line}: ${f.preview}`
|
|
631
|
+
}));
|
|
632
|
+
return {
|
|
633
|
+
exitCode: ExitCode.SENSITIVE_CONTENT_DETECTED,
|
|
634
|
+
result: ok({
|
|
635
|
+
schema: det.schema,
|
|
636
|
+
valid: false,
|
|
637
|
+
errors,
|
|
638
|
+
index_updated: false,
|
|
639
|
+
log_updated: false,
|
|
640
|
+
humanHint: `SENSITIVE CONTENT (${sensitiveFindings.length})
|
|
641
|
+
${errors.map((e) => ` ${e.message}`).join("\n")}`,
|
|
642
|
+
sensitive_findings: sensitiveFindings
|
|
643
|
+
})
|
|
644
|
+
};
|
|
645
|
+
}
|
|
483
646
|
if (!det.schema) {
|
|
484
647
|
return { exitCode: ExitCode.SCHEMA_NOT_DETECTED, result: ok({ schema: null, valid: false, errors: [], index_updated: false, log_updated: false, humanHint: "schema not detected" }) };
|
|
485
648
|
}
|
|
@@ -2704,6 +2867,7 @@ ${content}
|
|
|
2704
2867
|
// src/commands/lint.ts
|
|
2705
2868
|
import { existsSync as existsSync6 } from "fs";
|
|
2706
2869
|
import { readFile as readFile17 } from "fs/promises";
|
|
2870
|
+
import { createHash as createHash4 } from "crypto";
|
|
2707
2871
|
import { join as join23 } from "path";
|
|
2708
2872
|
|
|
2709
2873
|
// src/commands/sparse-community.ts
|
|
@@ -2920,7 +3084,7 @@ async function safeWritePage(absPath, newContent, opts = {}) {
|
|
|
2920
3084
|
}
|
|
2921
3085
|
|
|
2922
3086
|
// src/commands/raw-body-dedup.ts
|
|
2923
|
-
import { createHash as
|
|
3087
|
+
import { createHash as createHash3 } from "crypto";
|
|
2924
3088
|
async function runRawBodyDedup(vault) {
|
|
2925
3089
|
const scan = await scanVault(vault);
|
|
2926
3090
|
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
@@ -2931,7 +3095,7 @@ async function runRawBodyDedup(vault) {
|
|
|
2931
3095
|
const split = splitFrontmatter(text);
|
|
2932
3096
|
if (!split.ok) continue;
|
|
2933
3097
|
totalFiles++;
|
|
2934
|
-
const bodyHash =
|
|
3098
|
+
const bodyHash = createHash3("sha256").update(split.data.body).digest("hex");
|
|
2935
3099
|
const fm = extractFrontmatter(text);
|
|
2936
3100
|
let fmSha256 = null;
|
|
2937
3101
|
if (fm.ok && typeof fm.data.sha256 === "string" && fm.data.sha256.length === 64) {
|
|
@@ -3387,7 +3551,7 @@ function extractSourceEntries(rawFm) {
|
|
|
3387
3551
|
}
|
|
3388
3552
|
return entries;
|
|
3389
3553
|
}
|
|
3390
|
-
var ERROR_ORDER = ["broken_wikilinks", "invalid_frontmatter", "raw_source_identity_conflict", "raw_dedup", "broken_sources", "tag_not_in_taxonomy", "path_too_long"];
|
|
3554
|
+
var ERROR_ORDER = ["sensitive_content", "broken_wikilinks", "invalid_frontmatter", "raw_source_identity_conflict", "raw_dedup", "broken_sources", "tag_not_in_taxonomy", "path_too_long"];
|
|
3391
3555
|
var WARNING_ORDER = ["raw_body_duplicate", "raw_subdirectory_duplicate", "file_source_url", "index_incomplete", "index_link_format", "stale_page", "page_too_large", "log_rotate_needed", "orphans", "compound_refs", "legacy_citation_style", "orphaned_citations", "duplicate_frontmatter", "work_item_health", "orphaned_project_pages", "missing_overview", "missing_diagram"];
|
|
3392
3556
|
var INFO_ORDER = ["bridges", "sparse_community", "page_structure", "topic_map_recommended", "frontmatter_wikilink", "wikilink_citation", "missing_tldr", "stale_sections", "cli_refs"];
|
|
3393
3557
|
var KNOWN_BUCKETS = [...ERROR_ORDER, ...WARNING_ORDER, ...INFO_ORDER];
|
|
@@ -3416,6 +3580,26 @@ function summarizeBucket(bucket, severity, vaultPath, examplesLimit) {
|
|
|
3416
3580
|
details_command: `skillwiki lint ${shellQuote(vaultPath)} --only ${bucket.kind}`
|
|
3417
3581
|
};
|
|
3418
3582
|
}
|
|
3583
|
+
function recomputeRawSha256IfPresent(content) {
|
|
3584
|
+
const split = splitFrontmatter(content);
|
|
3585
|
+
if (!split.ok) return content;
|
|
3586
|
+
if (!/^sha256:\s*[0-9a-f]{64}$/m.test(split.data.rawFrontmatter)) return content;
|
|
3587
|
+
const sha256 = createHash4("sha256").update(Buffer.from(split.data.body, "utf8")).digest("hex");
|
|
3588
|
+
const rawFrontmatter = split.data.rawFrontmatter.replace(/^sha256:\s*[0-9a-f]{64}$/m, `sha256: ${sha256}`);
|
|
3589
|
+
return `---
|
|
3590
|
+
${rawFrontmatter}
|
|
3591
|
+
---
|
|
3592
|
+
${split.data.body}`;
|
|
3593
|
+
}
|
|
3594
|
+
function appendLintFixLastOp(vault, fixed) {
|
|
3595
|
+
if (fixed.length === 0) return;
|
|
3596
|
+
appendLastOp(vault, {
|
|
3597
|
+
operation: "lint-fix",
|
|
3598
|
+
summary: `fixed ${fixed.length} page(s)`,
|
|
3599
|
+
files: fixed,
|
|
3600
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3601
|
+
});
|
|
3602
|
+
}
|
|
3419
3603
|
function summarizeLintOutput(output, examplesLimit = 3) {
|
|
3420
3604
|
const buckets = [
|
|
3421
3605
|
...output.by_severity.error.map((bucket) => summarizeBucket(bucket, "error", output.vault.path, examplesLimit)),
|
|
@@ -3517,6 +3701,16 @@ async function runLint(input) {
|
|
|
3517
3701
|
const allPages = scan.ok ? [...scan.data.typedKnowledge, ...scan.data.raw, ...scan.data.workItems, ...scan.data.compound] : [];
|
|
3518
3702
|
const slugs = scan.ok ? buildSlugMap(allPages) : /* @__PURE__ */ new Map();
|
|
3519
3703
|
if (scan.ok) {
|
|
3704
|
+
const sensitiveFlags = [];
|
|
3705
|
+
for (const page of scan.data.allMarkdown) {
|
|
3706
|
+
try {
|
|
3707
|
+
const text = await readPage(page);
|
|
3708
|
+
const findings = scanSensitiveContent(text, { file: page.relPath });
|
|
3709
|
+
sensitiveFlags.push(...findings);
|
|
3710
|
+
} catch {
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3713
|
+
if (sensitiveFlags.length > 0) buckets.sensitive_content = sensitiveFlags;
|
|
3520
3714
|
const subDirDupes = [];
|
|
3521
3715
|
const flatStems = /* @__PURE__ */ new Map();
|
|
3522
3716
|
const deepFiles = [];
|
|
@@ -3732,6 +3926,36 @@ async function runLint(input) {
|
|
|
3732
3926
|
}
|
|
3733
3927
|
}
|
|
3734
3928
|
if (staleSectionFlags.length > 0) buckets.stale_sections = staleSectionFlags;
|
|
3929
|
+
if (shouldFix("sensitive_content") && buckets.sensitive_content) {
|
|
3930
|
+
const sensitiveFixed = [];
|
|
3931
|
+
for (const page of scan.data.allMarkdown) {
|
|
3932
|
+
try {
|
|
3933
|
+
const raw = await readPage(page);
|
|
3934
|
+
const redacted = redactSensitiveContent(raw, { file: page.relPath });
|
|
3935
|
+
if (!redacted.changed) continue;
|
|
3936
|
+
const next = recomputeRawSha256IfPresent(redacted.text);
|
|
3937
|
+
const w = await safeWritePage(page.absPath, next, { minBodyRatio: null });
|
|
3938
|
+
if (!w.ok) {
|
|
3939
|
+
unresolved.push(page.relPath);
|
|
3940
|
+
continue;
|
|
3941
|
+
}
|
|
3942
|
+
sensitiveFixed.push(page.relPath);
|
|
3943
|
+
} catch {
|
|
3944
|
+
unresolved.push(page.relPath);
|
|
3945
|
+
}
|
|
3946
|
+
}
|
|
3947
|
+
fixed.push(...sensitiveFixed);
|
|
3948
|
+
const remainingSensitiveFlags = [];
|
|
3949
|
+
for (const page of scan.data.allMarkdown) {
|
|
3950
|
+
try {
|
|
3951
|
+
const text = await readPage(page);
|
|
3952
|
+
remainingSensitiveFlags.push(...scanSensitiveContent(text, { file: page.relPath }));
|
|
3953
|
+
} catch {
|
|
3954
|
+
}
|
|
3955
|
+
}
|
|
3956
|
+
if (remainingSensitiveFlags.length > 0) buckets.sensitive_content = remainingSensitiveFlags;
|
|
3957
|
+
else delete buckets.sensitive_content;
|
|
3958
|
+
}
|
|
3735
3959
|
if (shouldFix("legacy_citation_style") && legacyPages.length > 0) {
|
|
3736
3960
|
const FENCE_RE2 = /```[\s\S]*?```/g;
|
|
3737
3961
|
const INLINE_MARKER = /\^\[raw\/[^\]]+\]/g;
|
|
@@ -4088,6 +4312,7 @@ ${newBody}`;
|
|
|
4088
4312
|
humanHint: `--only ${input.only}
|
|
4089
4313
|
${match.length === 0 ? "0 violations" : match.map((b) => ` ${b.kind}: ${b.items.length}`).join("\n")}`
|
|
4090
4314
|
};
|
|
4315
|
+
if (input.fix) appendLintFixLastOp(input.vault, fixed);
|
|
4091
4316
|
return {
|
|
4092
4317
|
exitCode: fExit,
|
|
4093
4318
|
result: ok(input.summary ? summarizeLintOutput(output2, input.examplesLimit) : output2)
|
|
@@ -4110,14 +4335,7 @@ ${match.length === 0 ? "0 violations" : match.map((b) => ` ${b.kind}: ${b.items
|
|
|
4110
4335
|
hintLines.push(` ${b.kind}: ${b.items.length}`);
|
|
4111
4336
|
}
|
|
4112
4337
|
if (hintLines.length === 0) hintLines.push("0 errors, 0 warnings, 0 info");
|
|
4113
|
-
if (input.fix
|
|
4114
|
-
appendLastOp(input.vault, {
|
|
4115
|
-
operation: "lint-fix",
|
|
4116
|
-
summary: `fixed ${fixed.length} page(s)`,
|
|
4117
|
-
files: fixed,
|
|
4118
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4119
|
-
});
|
|
4120
|
-
}
|
|
4338
|
+
if (input.fix) appendLintFixLastOp(input.vault, fixed);
|
|
4121
4339
|
const output = {
|
|
4122
4340
|
vault: { path: input.vault, source: input.source ?? "resolved" },
|
|
4123
4341
|
summary,
|
|
@@ -4134,12 +4352,12 @@ ${match.length === 0 ? "0 violations" : match.map((b) => ` ${b.kind}: ${b.items
|
|
|
4134
4352
|
|
|
4135
4353
|
// src/commands/health.ts
|
|
4136
4354
|
import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync8, renameSync, writeFileSync as writeFileSync6 } from "fs";
|
|
4137
|
-
import { dirname as dirname10, join as
|
|
4355
|
+
import { dirname as dirname10, join as join30, resolve as resolve6 } from "path";
|
|
4138
4356
|
import { platform as platform3 } from "os";
|
|
4139
4357
|
|
|
4140
4358
|
// src/commands/doctor.ts
|
|
4141
4359
|
import { existsSync as existsSync11, lstatSync, readlinkSync, readdirSync as readdirSync2, statSync as statSync3, readFileSync as readFileSync7 } from "fs";
|
|
4142
|
-
import { join as
|
|
4360
|
+
import { join as join29, resolve as resolve5 } from "path";
|
|
4143
4361
|
import { execSync as execSync2 } from "child_process";
|
|
4144
4362
|
import { platform as platform2 } from "os";
|
|
4145
4363
|
|
|
@@ -4381,135 +4599,535 @@ function parseTomlScalar(rawValue) {
|
|
|
4381
4599
|
return value;
|
|
4382
4600
|
}
|
|
4383
4601
|
|
|
4384
|
-
// src/
|
|
4385
|
-
import {
|
|
4386
|
-
import {
|
|
4387
|
-
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, unlinkSync as unlinkSync4, readFileSync as readFile19 } from "fs";
|
|
4602
|
+
// src/commands/fleet.ts
|
|
4603
|
+
import { readFile as readFile19 } from "fs/promises";
|
|
4604
|
+
import { hostname as nodeHostname, userInfo } from "os";
|
|
4388
4605
|
import { join as join27 } from "path";
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
}).trim();
|
|
4397
|
-
const pids = out.split("\n").filter(Boolean);
|
|
4398
|
-
if (pids.length === 0) return null;
|
|
4399
|
-
return parseInt(pids[0], 10);
|
|
4400
|
-
} catch {
|
|
4401
|
-
try {
|
|
4402
|
-
const out = execSync("ps aux", { encoding: "utf8", timeout: 2e3, stdio: ["pipe", "pipe", "pipe"] });
|
|
4403
|
-
for (const line of out.split("\n")) {
|
|
4404
|
-
if (line.includes("rclone") && line.includes("mount") && !line.includes("grep")) {
|
|
4405
|
-
const parts = line.trim().split(/\s+/);
|
|
4406
|
-
if (parts.length >= 2) return parseInt(parts[1], 10);
|
|
4407
|
-
}
|
|
4408
|
-
}
|
|
4409
|
-
} catch {
|
|
4606
|
+
import yaml3 from "js-yaml";
|
|
4607
|
+
var FLEET_REL_PATH = join27("projects", "llm-wiki", "architecture", "fleet.yaml");
|
|
4608
|
+
async function runFleetValidate(input) {
|
|
4609
|
+
const loaded = await loadFleetManifest(input.file);
|
|
4610
|
+
if (!loaded.ok) {
|
|
4611
|
+
if (loaded.error === "FILE_NOT_FOUND") {
|
|
4612
|
+
return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: input.file }) };
|
|
4410
4613
|
}
|
|
4411
|
-
|
|
4614
|
+
const errors = fleetLoadErrors(loaded);
|
|
4615
|
+
return invalidFleet(errors);
|
|
4412
4616
|
}
|
|
4617
|
+
const warnings = fleetWarnings(loaded.manifest);
|
|
4618
|
+
const snapshotter = findSnapshotter(loaded.manifest);
|
|
4619
|
+
return {
|
|
4620
|
+
exitCode: ExitCode.OK,
|
|
4621
|
+
result: ok({
|
|
4622
|
+
valid: true,
|
|
4623
|
+
errors: [],
|
|
4624
|
+
warnings,
|
|
4625
|
+
host_count: Object.keys(loaded.manifest.hosts).length,
|
|
4626
|
+
snapshotter,
|
|
4627
|
+
humanHint: `VALID fleet manifest (${Object.keys(loaded.manifest.hosts).length} hosts; snapshotter: ${snapshotter ?? "none"})`
|
|
4628
|
+
})
|
|
4629
|
+
};
|
|
4413
4630
|
}
|
|
4414
|
-
function
|
|
4415
|
-
const
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4631
|
+
async function runFleetContext(input) {
|
|
4632
|
+
const env = input.env ?? process.env;
|
|
4633
|
+
const home = input.home ?? env.HOME ?? "";
|
|
4634
|
+
const cwd = input.cwd ?? process.cwd();
|
|
4635
|
+
const osHostname = input.osHostname ?? safeEnvValue(env.HOSTNAME) ?? nodeHostname();
|
|
4636
|
+
const user = input.user ?? safeEnvValue(env.USER) ?? safeUserName();
|
|
4637
|
+
const vault = input.vault ?? safeEnvValue(env.WIKI_PATH);
|
|
4638
|
+
const file = input.file ?? (vault ? join27(vault, FLEET_REL_PATH) : void 0);
|
|
4639
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4640
|
+
const loaded = file ? await loadFleetManifest(file) : { ok: false, error: "FILE_NOT_FOUND" };
|
|
4641
|
+
if (!loaded.ok) {
|
|
4642
|
+
const warnings = ["fleet manifest unavailable or invalid"];
|
|
4643
|
+
const markdown2 = formatUnknownContext({
|
|
4644
|
+
generatedAt,
|
|
4645
|
+
osHostname,
|
|
4646
|
+
user,
|
|
4647
|
+
cwd,
|
|
4648
|
+
vault,
|
|
4649
|
+
reason: warnings[0],
|
|
4650
|
+
trace: [],
|
|
4651
|
+
warnings
|
|
4652
|
+
});
|
|
4653
|
+
return {
|
|
4654
|
+
exitCode: ExitCode.OK,
|
|
4655
|
+
result: ok({
|
|
4656
|
+
manifest_loaded: false,
|
|
4657
|
+
generated_at: generatedAt,
|
|
4658
|
+
identity_status: "unknown",
|
|
4659
|
+
resolver_trace: [],
|
|
4660
|
+
warnings,
|
|
4661
|
+
markdown: markdown2,
|
|
4662
|
+
humanHint: markdown2
|
|
4663
|
+
})
|
|
4664
|
+
};
|
|
4434
4665
|
}
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4666
|
+
const resolved = await resolveHostId({
|
|
4667
|
+
manifest: loaded.manifest,
|
|
4668
|
+
hostId: input.hostId,
|
|
4669
|
+
env,
|
|
4670
|
+
home,
|
|
4671
|
+
osHostname
|
|
4672
|
+
});
|
|
4673
|
+
if (resolved.hostId && !loaded.manifest.hosts[resolved.hostId]) {
|
|
4674
|
+
const source = resolved.source ?? "unknown";
|
|
4675
|
+
const warnings = [`resolved host id \`${resolved.hostId}\` from ${source} is not in fleet.yaml`];
|
|
4676
|
+
const markdown2 = formatInvalidContext({
|
|
4677
|
+
generatedAt,
|
|
4678
|
+
hostId: resolved.hostId,
|
|
4679
|
+
source,
|
|
4680
|
+
osHostname,
|
|
4681
|
+
user,
|
|
4682
|
+
cwd,
|
|
4683
|
+
vault,
|
|
4684
|
+
trace: resolved.trace,
|
|
4685
|
+
warnings
|
|
4443
4686
|
});
|
|
4444
|
-
const match = out.match(/rclone\s+v(\d+)\.(\d+)\.(\d+)/i);
|
|
4445
|
-
if (!match) return null;
|
|
4446
4687
|
return {
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4688
|
+
exitCode: ExitCode.OK,
|
|
4689
|
+
result: ok({
|
|
4690
|
+
manifest_loaded: true,
|
|
4691
|
+
host_id: resolved.hostId,
|
|
4692
|
+
source: resolved.source,
|
|
4693
|
+
generated_at: generatedAt,
|
|
4694
|
+
identity_status: "invalid",
|
|
4695
|
+
resolver_trace: resolved.trace,
|
|
4696
|
+
warnings,
|
|
4697
|
+
markdown: markdown2,
|
|
4698
|
+
humanHint: markdown2
|
|
4699
|
+
})
|
|
4451
4700
|
};
|
|
4452
|
-
} catch {
|
|
4453
|
-
return null;
|
|
4454
4701
|
}
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4702
|
+
if (!resolved.hostId) {
|
|
4703
|
+
const warnings = ["host identity is unresolved"];
|
|
4704
|
+
const markdown2 = formatUnknownContext({
|
|
4705
|
+
generatedAt,
|
|
4706
|
+
osHostname,
|
|
4707
|
+
user,
|
|
4708
|
+
cwd,
|
|
4709
|
+
vault,
|
|
4710
|
+
reason: warnings[0],
|
|
4711
|
+
trace: resolved.trace,
|
|
4712
|
+
warnings
|
|
4713
|
+
});
|
|
4714
|
+
return {
|
|
4715
|
+
exitCode: ExitCode.OK,
|
|
4716
|
+
result: ok({
|
|
4717
|
+
manifest_loaded: true,
|
|
4718
|
+
generated_at: generatedAt,
|
|
4719
|
+
identity_status: "unknown",
|
|
4720
|
+
resolver_trace: resolved.trace,
|
|
4721
|
+
warnings,
|
|
4722
|
+
markdown: markdown2,
|
|
4723
|
+
humanHint: markdown2
|
|
4724
|
+
})
|
|
4725
|
+
};
|
|
4466
4726
|
}
|
|
4467
|
-
|
|
4727
|
+
const markdown = formatKnownContext({
|
|
4728
|
+
manifest: loaded.manifest,
|
|
4729
|
+
hostId: resolved.hostId,
|
|
4730
|
+
source: resolved.source,
|
|
4731
|
+
generatedAt,
|
|
4732
|
+
osHostname,
|
|
4733
|
+
user,
|
|
4734
|
+
cwd,
|
|
4735
|
+
vault,
|
|
4736
|
+
trace: resolved.trace
|
|
4737
|
+
});
|
|
4738
|
+
return {
|
|
4739
|
+
exitCode: ExitCode.OK,
|
|
4740
|
+
result: ok({
|
|
4741
|
+
manifest_loaded: true,
|
|
4742
|
+
host_id: resolved.hostId,
|
|
4743
|
+
source: resolved.source,
|
|
4744
|
+
generated_at: generatedAt,
|
|
4745
|
+
identity_status: "known",
|
|
4746
|
+
resolver_trace: resolved.trace,
|
|
4747
|
+
warnings: [],
|
|
4748
|
+
markdown,
|
|
4749
|
+
humanHint: markdown
|
|
4750
|
+
})
|
|
4751
|
+
};
|
|
4468
4752
|
}
|
|
4469
|
-
function
|
|
4753
|
+
async function loadFleetManifest(file) {
|
|
4754
|
+
let text;
|
|
4470
4755
|
try {
|
|
4471
|
-
|
|
4472
|
-
const raw = readFileSync6(`/proc/${pid}/cmdline`);
|
|
4473
|
-
return new TextDecoder().decode(raw).split("\0").filter(Boolean);
|
|
4474
|
-
} else {
|
|
4475
|
-
const out = execSync(`ps -o args= -p ${pid}`, {
|
|
4476
|
-
encoding: "utf8",
|
|
4477
|
-
timeout: 2e3,
|
|
4478
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
4479
|
-
}).trim();
|
|
4480
|
-
return out.split(/\s+/);
|
|
4481
|
-
}
|
|
4756
|
+
text = await readFile19(file, "utf8");
|
|
4482
4757
|
} catch {
|
|
4483
|
-
return
|
|
4758
|
+
return { ok: false, error: "FILE_NOT_FOUND" };
|
|
4484
4759
|
}
|
|
4485
|
-
|
|
4486
|
-
function queryRcloneRC(rcAddr, fs) {
|
|
4760
|
+
let parsed;
|
|
4487
4761
|
try {
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
{ encoding: "utf8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }
|
|
4492
|
-
);
|
|
4493
|
-
if (!out.trim()) return null;
|
|
4494
|
-
const data = JSON.parse(out);
|
|
4495
|
-
if (data.status && data.status >= 400) {
|
|
4496
|
-
return { error: data.error || `RC error (status ${data.status})`, erroredFiles: 0, uploadsInProgress: 0, uploadsQueued: 0, outOfSpace: false, bytesUsed: 0, files: 0, totalSize: "unknown" };
|
|
4497
|
-
}
|
|
4498
|
-
const dc = data.diskCache || {};
|
|
4499
|
-
return {
|
|
4500
|
-
erroredFiles: dc.erroredFiles ?? 0,
|
|
4501
|
-
uploadsInProgress: dc.uploadsInProgress ?? 0,
|
|
4502
|
-
uploadsQueued: dc.uploadsQueued ?? 0,
|
|
4503
|
-
outOfSpace: dc.outOfSpace ?? false,
|
|
4504
|
-
bytesUsed: dc.bytesUsed ?? 0,
|
|
4505
|
-
files: dc.files ?? 0,
|
|
4506
|
-
totalSize: data.totalSize || "unknown"
|
|
4507
|
-
};
|
|
4508
|
-
} catch {
|
|
4509
|
-
return { error: "RC endpoint unreachable", erroredFiles: 0, uploadsInProgress: 0, uploadsQueued: 0, outOfSpace: false, bytesUsed: 0, files: 0, totalSize: "unknown" };
|
|
4762
|
+
parsed = yaml3.load(text, { schema: yaml3.JSON_SCHEMA });
|
|
4763
|
+
} catch (error) {
|
|
4764
|
+
return { ok: false, error: "INVALID_YAML", detail: error instanceof Error ? error.message : String(error) };
|
|
4510
4765
|
}
|
|
4511
|
-
|
|
4512
|
-
|
|
4766
|
+
const result = FleetManifestSchema.safeParse(parsed);
|
|
4767
|
+
if (!result.success) {
|
|
4768
|
+
return { ok: false, error: "INVALID_FLEET_MANIFEST", detail: result.error.issues };
|
|
4769
|
+
}
|
|
4770
|
+
return { ok: true, manifest: result.data };
|
|
4771
|
+
}
|
|
4772
|
+
function invalidFleet(errors) {
|
|
4773
|
+
return {
|
|
4774
|
+
exitCode: ExitCode.FLEET_MANIFEST_INVALID,
|
|
4775
|
+
result: ok({
|
|
4776
|
+
valid: false,
|
|
4777
|
+
errors,
|
|
4778
|
+
warnings: [],
|
|
4779
|
+
host_count: 0,
|
|
4780
|
+
humanHint: `INVALID fleet manifest
|
|
4781
|
+
${errors.map((e) => ` ${e.path || "(root)"}: ${e.message}`).join("\n")}`
|
|
4782
|
+
})
|
|
4783
|
+
};
|
|
4784
|
+
}
|
|
4785
|
+
function fleetLoadErrors(loaded) {
|
|
4786
|
+
if (loaded.error === "INVALID_YAML") {
|
|
4787
|
+
return [{ path: "", message: `invalid YAML: ${String(loaded.detail ?? "parse failed")}` }];
|
|
4788
|
+
}
|
|
4789
|
+
if (loaded.error === "INVALID_FLEET_MANIFEST" && Array.isArray(loaded.detail)) {
|
|
4790
|
+
return loaded.detail.map((issue) => {
|
|
4791
|
+
const zodIssue = issue;
|
|
4792
|
+
return {
|
|
4793
|
+
path: (zodIssue.path ?? []).join("."),
|
|
4794
|
+
message: zodIssue.message ?? "invalid value"
|
|
4795
|
+
};
|
|
4796
|
+
});
|
|
4797
|
+
}
|
|
4798
|
+
return [{ path: "", message: loaded.error }];
|
|
4799
|
+
}
|
|
4800
|
+
function fleetWarnings(manifest) {
|
|
4801
|
+
const warnings = [];
|
|
4802
|
+
for (const [id, host] of Object.entries(manifest.hosts)) {
|
|
4803
|
+
if (host.role === "snapshotter" && host.protected !== true) {
|
|
4804
|
+
warnings.push(`snapshotter host '${id}' is not protected=true`);
|
|
4805
|
+
}
|
|
4806
|
+
}
|
|
4807
|
+
return warnings;
|
|
4808
|
+
}
|
|
4809
|
+
function findSnapshotter(manifest) {
|
|
4810
|
+
return Object.entries(manifest.hosts).find(([, host]) => host.role === "snapshotter")?.[0];
|
|
4811
|
+
}
|
|
4812
|
+
async function resolveHostId(input) {
|
|
4813
|
+
const trace = [];
|
|
4814
|
+
if (input.hostId) {
|
|
4815
|
+
trace.push({ source: "--host-id", status: "matched", value: input.hostId });
|
|
4816
|
+
return { hostId: input.hostId, source: "host-id", trace };
|
|
4817
|
+
}
|
|
4818
|
+
trace.push({ source: "--host-id", status: "unset" });
|
|
4819
|
+
if (input.env.SKILLWIKI_HOST_ID) {
|
|
4820
|
+
trace.push({ source: "SKILLWIKI_HOST_ID", status: "matched", value: input.env.SKILLWIKI_HOST_ID });
|
|
4821
|
+
return { hostId: input.env.SKILLWIKI_HOST_ID, source: "SKILLWIKI_HOST_ID", trace };
|
|
4822
|
+
}
|
|
4823
|
+
trace.push({ source: "SKILLWIKI_HOST_ID", status: "unset" });
|
|
4824
|
+
if (input.env.AGENT_HOST_ID) {
|
|
4825
|
+
trace.push({ source: "AGENT_HOST_ID", status: "matched", value: input.env.AGENT_HOST_ID });
|
|
4826
|
+
return { hostId: input.env.AGENT_HOST_ID, source: "AGENT_HOST_ID", trace };
|
|
4827
|
+
}
|
|
4828
|
+
trace.push({ source: "AGENT_HOST_ID", status: "unset" });
|
|
4829
|
+
if (input.home) {
|
|
4830
|
+
const dotenv = await parseDotenvFile(join27(input.home, ".skillwiki", ".env"));
|
|
4831
|
+
if (dotenv.SKILLWIKI_HOST_ID) {
|
|
4832
|
+
trace.push({ source: "~/.skillwiki/.env:SKILLWIKI_HOST_ID", status: "matched", value: dotenv.SKILLWIKI_HOST_ID });
|
|
4833
|
+
return { hostId: dotenv.SKILLWIKI_HOST_ID, source: "~/.skillwiki/.env:SKILLWIKI_HOST_ID", trace };
|
|
4834
|
+
}
|
|
4835
|
+
trace.push({ source: "~/.skillwiki/.env:SKILLWIKI_HOST_ID", status: "unset" });
|
|
4836
|
+
} else {
|
|
4837
|
+
trace.push({ source: "~/.skillwiki/.env:SKILLWIKI_HOST_ID", status: "skipped" });
|
|
4838
|
+
}
|
|
4839
|
+
if (input.env.VS_HOSTNAME) {
|
|
4840
|
+
trace.push({ source: "VS_HOSTNAME", status: "matched", value: input.env.VS_HOSTNAME });
|
|
4841
|
+
return { hostId: input.env.VS_HOSTNAME, source: "VS_HOSTNAME", trace };
|
|
4842
|
+
}
|
|
4843
|
+
trace.push({ source: "VS_HOSTNAME", status: "unset" });
|
|
4844
|
+
const hostname = input.osHostname.trim();
|
|
4845
|
+
if (hostname) {
|
|
4846
|
+
if (input.manifest.hosts[hostname]) {
|
|
4847
|
+
trace.push({ source: "hostname", status: "matched", value: hostname });
|
|
4848
|
+
return { hostId: hostname, source: "hostname", trace };
|
|
4849
|
+
}
|
|
4850
|
+
const byHostname = Object.entries(input.manifest.hosts).find(([, host]) => host.identity.hostnames.includes(hostname));
|
|
4851
|
+
if (byHostname) {
|
|
4852
|
+
trace.push({ source: "hostname", status: "matched", value: hostname });
|
|
4853
|
+
return { hostId: byHostname[0], source: "hostname", trace };
|
|
4854
|
+
}
|
|
4855
|
+
trace.push({ source: "hostname", status: "unmatched", value: hostname });
|
|
4856
|
+
} else {
|
|
4857
|
+
trace.push({ source: "hostname", status: "unset" });
|
|
4858
|
+
}
|
|
4859
|
+
return { trace };
|
|
4860
|
+
}
|
|
4861
|
+
function formatKnownContext(input) {
|
|
4862
|
+
const host = input.manifest.hosts[input.hostId];
|
|
4863
|
+
const protectedValue = host.protected === true ? "true" : "false";
|
|
4864
|
+
const writesTo = host.writes_to.join(", ");
|
|
4865
|
+
const selfAliases = collectSelfAliases(input.manifest, input.hostId);
|
|
4866
|
+
const outbound = collectOutboundAccess(input.manifest, input.hostId);
|
|
4867
|
+
const maintenanceLines = formatMaintenanceLines(host);
|
|
4868
|
+
const guidance = input.hostId === "macos-dev" ? "use declared SSH aliases for remote work when needed; do not assume undeclared hosts have reciprocal SSH access." : `this session is already on \`${input.hostId}\`; do not SSH to self aliases unless the user explicitly asks. Do not assume outbound SSH to other fleet hosts is configured.`;
|
|
4869
|
+
return [
|
|
4870
|
+
"## Runtime Host Context",
|
|
4871
|
+
"",
|
|
4872
|
+
`- Context generated: \`${input.generatedAt}\``,
|
|
4873
|
+
`- Current machine: \`${input.hostId}\`${input.source ? ` (source: \`${input.source}\`)` : ""}`,
|
|
4874
|
+
"- Identity status: `known`",
|
|
4875
|
+
`- Identity resolution: ${formatResolution(input.source, input.hostId)}`,
|
|
4876
|
+
`- Resolver trace: ${formatTrace(input.trace)}`,
|
|
4877
|
+
`- OS hostname: ${formatMaybe(input.osHostname)}`,
|
|
4878
|
+
`- User: ${formatMaybe(input.user)}`,
|
|
4879
|
+
`- Workspace: ${formatMaybe(input.cwd)}`,
|
|
4880
|
+
`- Vault: ${formatMaybe(input.vault)}`,
|
|
4881
|
+
"- Remote freshness: not checked by `fleet context`; run `sync status` or presync before host-sensitive work.",
|
|
4882
|
+
`- Fleet role: \`${host.role}\`; protected: \`${protectedValue}\`; writes_to: \`${writesTo}\``,
|
|
4883
|
+
...maintenanceLines,
|
|
4884
|
+
`- Self SSH aliases known in fleet: ${formatList(selfAliases)}`,
|
|
4885
|
+
`- Declared outbound SSH from this source: ${formatOutboundAccess(outbound)}`,
|
|
4886
|
+
`- Guidance: ${guidance}`
|
|
4887
|
+
].join("\n");
|
|
4888
|
+
}
|
|
4889
|
+
function formatUnknownContext(input) {
|
|
4890
|
+
return [
|
|
4891
|
+
"## Runtime Host Context",
|
|
4892
|
+
"",
|
|
4893
|
+
`- Context generated: \`${input.generatedAt}\``,
|
|
4894
|
+
"- Current machine: unknown",
|
|
4895
|
+
"- Identity status: `unknown`",
|
|
4896
|
+
`- Resolver trace: ${formatTrace(input.trace)}`,
|
|
4897
|
+
`- Warnings: ${formatWarnings(input.warnings)}`,
|
|
4898
|
+
`- OS hostname: ${formatMaybe(input.osHostname)}`,
|
|
4899
|
+
`- User: ${formatMaybe(input.user)}`,
|
|
4900
|
+
`- Workspace: ${formatMaybe(input.cwd)}`,
|
|
4901
|
+
`- Vault: ${formatMaybe(input.vault)}`,
|
|
4902
|
+
"- Remote freshness: not checked by `fleet context`; run `sync status` or presync before host-sensitive work.",
|
|
4903
|
+
"- Fleet role: unknown",
|
|
4904
|
+
"- Self SSH aliases known in fleet: unknown",
|
|
4905
|
+
"- Declared outbound SSH from this source: unknown",
|
|
4906
|
+
`- Guidance: ${input.reason}; do not assume local vs remote role. Inspect runtime or ask before SSH/deploy/sync work.`
|
|
4907
|
+
].join("\n");
|
|
4908
|
+
}
|
|
4909
|
+
function formatInvalidContext(input) {
|
|
4910
|
+
return [
|
|
4911
|
+
"## Runtime Host Context",
|
|
4912
|
+
"",
|
|
4913
|
+
`- Context generated: \`${input.generatedAt}\``,
|
|
4914
|
+
"- Current machine: unknown",
|
|
4915
|
+
"- Identity status: `invalid`",
|
|
4916
|
+
`- Identity resolution: ${formatResolution(input.source, input.hostId)}`,
|
|
4917
|
+
`- Resolver trace: ${formatTrace(input.trace)}`,
|
|
4918
|
+
`- Warnings: ${formatWarnings(input.warnings)}`,
|
|
4919
|
+
`- OS hostname: ${formatMaybe(input.osHostname)}`,
|
|
4920
|
+
`- User: ${formatMaybe(input.user)}`,
|
|
4921
|
+
`- Workspace: ${formatMaybe(input.cwd)}`,
|
|
4922
|
+
`- Vault: ${formatMaybe(input.vault)}`,
|
|
4923
|
+
"- Remote freshness: not checked by `fleet context`; run `sync status` or presync before host-sensitive work.",
|
|
4924
|
+
"- Fleet role: unknown",
|
|
4925
|
+
"- Self SSH aliases known in fleet: unknown",
|
|
4926
|
+
"- Declared outbound SSH from this source: unknown",
|
|
4927
|
+
`- Guidance: do not trust this identity; rerun with \`--host-id\` only if the user confirms \`${input.hostId}\` is the current fleet host id.`
|
|
4928
|
+
].join("\n");
|
|
4929
|
+
}
|
|
4930
|
+
function collectSelfAliases(manifest, hostId2) {
|
|
4931
|
+
const aliases = [];
|
|
4932
|
+
const host = manifest.hosts[hostId2];
|
|
4933
|
+
const access = host?.access?.from ?? {};
|
|
4934
|
+
for (const profile of Object.values(access)) {
|
|
4935
|
+
for (const alias of profile.ssh_aliases ?? []) aliases.push(alias);
|
|
4936
|
+
}
|
|
4937
|
+
return [...new Set(aliases)];
|
|
4938
|
+
}
|
|
4939
|
+
function collectOutboundAccess(manifest, sourceHostId) {
|
|
4940
|
+
const hosts = [];
|
|
4941
|
+
for (const [targetId, target] of Object.entries(manifest.hosts)) {
|
|
4942
|
+
if (targetId === sourceHostId) continue;
|
|
4943
|
+
const profile = target.access?.from?.[sourceHostId];
|
|
4944
|
+
if (profile && (profile.status === "configured" || profile.status === "local")) {
|
|
4945
|
+
hosts.push({
|
|
4946
|
+
hostId: targetId,
|
|
4947
|
+
sshAliases: [...new Set(profile.ssh_aliases ?? [])],
|
|
4948
|
+
users: [...new Set(profile.users ?? [])]
|
|
4949
|
+
});
|
|
4950
|
+
}
|
|
4951
|
+
}
|
|
4952
|
+
return hosts.sort((left, right) => left.hostId.localeCompare(right.hostId));
|
|
4953
|
+
}
|
|
4954
|
+
function formatMaintenanceLines(host) {
|
|
4955
|
+
const satellite = host.maintenance?.skillwiki_satellite;
|
|
4956
|
+
if (!satellite?.enabled) return [];
|
|
4957
|
+
return [
|
|
4958
|
+
`- Maintenance role: \`skillwiki satellite\`; user: \`${satellite.user}\`; ssh: \`${satellite.ssh_alias}\``,
|
|
4959
|
+
`- Maintenance paths: maintenance vault: \`${satellite.vault_path}\`; repo: \`${satellite.repo_path}\`; scheduler: \`${satellite.scheduler}\`; jobs: ${formatList(satellite.jobs)}`
|
|
4960
|
+
];
|
|
4961
|
+
}
|
|
4962
|
+
function formatOutboundAccess(values) {
|
|
4963
|
+
if (values.length === 0) return "none";
|
|
4964
|
+
return values.map((value) => {
|
|
4965
|
+
const aliasPart = value.sshAliases.length > 0 ? ` via ${formatList(value.sshAliases)}` : " (no SSH aliases)";
|
|
4966
|
+
const usersPart = value.users.length > 0 ? ` (users: ${formatList(value.users)})` : "";
|
|
4967
|
+
return `\`${value.hostId}\`${aliasPart}${usersPart}`;
|
|
4968
|
+
}).join("; ");
|
|
4969
|
+
}
|
|
4970
|
+
function formatResolution(source, hostId2) {
|
|
4971
|
+
return source ? `\`${source === "host-id" ? "--host-id" : source}\` -> \`${hostId2}\`` : `unknown -> \`${hostId2}\``;
|
|
4972
|
+
}
|
|
4973
|
+
function formatTrace(values) {
|
|
4974
|
+
if (values.length === 0) return "not available";
|
|
4975
|
+
return values.map((value) => {
|
|
4976
|
+
const source = `\`${value.source}\``;
|
|
4977
|
+
if (value.status === "matched") return `${source} matched \`${value.value ?? ""}\``;
|
|
4978
|
+
if (value.status === "unmatched") return `${source} unmatched \`${value.value ?? ""}\``;
|
|
4979
|
+
return `${source} ${value.status}`;
|
|
4980
|
+
}).join("; ");
|
|
4981
|
+
}
|
|
4982
|
+
function formatWarnings(values) {
|
|
4983
|
+
return values.length > 0 ? values.join("; ") : "none";
|
|
4984
|
+
}
|
|
4985
|
+
function formatList(values) {
|
|
4986
|
+
return values.length > 0 ? values.map((v) => `\`${v}\``).join(", ") : "none";
|
|
4987
|
+
}
|
|
4988
|
+
function formatMaybe(value) {
|
|
4989
|
+
return value && value.trim().length > 0 ? `\`${value}\`` : "unknown";
|
|
4990
|
+
}
|
|
4991
|
+
function safeEnvValue(value) {
|
|
4992
|
+
return value && value.trim().length > 0 ? value : void 0;
|
|
4993
|
+
}
|
|
4994
|
+
function safeUserName() {
|
|
4995
|
+
try {
|
|
4996
|
+
return userInfo().username;
|
|
4997
|
+
} catch {
|
|
4998
|
+
return "";
|
|
4999
|
+
}
|
|
5000
|
+
}
|
|
5001
|
+
|
|
5002
|
+
// src/utils/s3-mount-health.ts
|
|
5003
|
+
import { execSync } from "child_process";
|
|
5004
|
+
import { platform } from "os";
|
|
5005
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, unlinkSync as unlinkSync4, readFileSync as readFile20 } from "fs";
|
|
5006
|
+
import { join as join28 } from "path";
|
|
5007
|
+
var OS = platform();
|
|
5008
|
+
function findRcloneMountPid() {
|
|
5009
|
+
try {
|
|
5010
|
+
const out = execSync("pgrep -f 'rclone.*mount'", {
|
|
5011
|
+
encoding: "utf8",
|
|
5012
|
+
timeout: 2e3,
|
|
5013
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5014
|
+
}).trim();
|
|
5015
|
+
const pids = out.split("\n").filter(Boolean);
|
|
5016
|
+
if (pids.length === 0) return null;
|
|
5017
|
+
return parseInt(pids[0], 10);
|
|
5018
|
+
} catch {
|
|
5019
|
+
try {
|
|
5020
|
+
const out = execSync("ps aux", { encoding: "utf8", timeout: 2e3, stdio: ["pipe", "pipe", "pipe"] });
|
|
5021
|
+
for (const line of out.split("\n")) {
|
|
5022
|
+
if (line.includes("rclone") && line.includes("mount") && !line.includes("grep")) {
|
|
5023
|
+
const parts = line.trim().split(/\s+/);
|
|
5024
|
+
if (parts.length >= 2) return parseInt(parts[1], 10);
|
|
5025
|
+
}
|
|
5026
|
+
}
|
|
5027
|
+
} catch {
|
|
5028
|
+
}
|
|
5029
|
+
return null;
|
|
5030
|
+
}
|
|
5031
|
+
}
|
|
5032
|
+
function parseRcloneFlags(pid) {
|
|
5033
|
+
const flags = /* @__PURE__ */ new Map();
|
|
5034
|
+
try {
|
|
5035
|
+
const args = getRcloneArgs(pid);
|
|
5036
|
+
for (let i = 0; i < args.length; i++) {
|
|
5037
|
+
const arg = args[i];
|
|
5038
|
+
if (arg.startsWith("--") && arg.includes("=")) {
|
|
5039
|
+
const eq = arg.indexOf("=");
|
|
5040
|
+
flags.set(arg.slice(0, eq), arg.slice(eq + 1));
|
|
5041
|
+
} else if (arg.startsWith("--")) {
|
|
5042
|
+
const next = args[i + 1];
|
|
5043
|
+
if (next && !next.startsWith("-")) {
|
|
5044
|
+
flags.set(arg, next);
|
|
5045
|
+
i++;
|
|
5046
|
+
} else {
|
|
5047
|
+
flags.set(arg, "");
|
|
5048
|
+
}
|
|
5049
|
+
}
|
|
5050
|
+
}
|
|
5051
|
+
} catch {
|
|
5052
|
+
}
|
|
5053
|
+
return flags;
|
|
5054
|
+
}
|
|
5055
|
+
function getRcloneVersion() {
|
|
5056
|
+
try {
|
|
5057
|
+
const out = execSync("rclone version", {
|
|
5058
|
+
encoding: "utf8",
|
|
5059
|
+
timeout: 3e3,
|
|
5060
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5061
|
+
});
|
|
5062
|
+
const match = out.match(/rclone\s+v(\d+)\.(\d+)\.(\d+)/i);
|
|
5063
|
+
if (!match) return null;
|
|
5064
|
+
return {
|
|
5065
|
+
major: parseInt(match[1], 10),
|
|
5066
|
+
minor: parseInt(match[2], 10),
|
|
5067
|
+
patch: parseInt(match[3], 10),
|
|
5068
|
+
raw: out.split("\n")[0].trim()
|
|
5069
|
+
};
|
|
5070
|
+
} catch {
|
|
5071
|
+
return null;
|
|
5072
|
+
}
|
|
5073
|
+
}
|
|
5074
|
+
function extractRcloneFs(args) {
|
|
5075
|
+
let foundMount = false;
|
|
5076
|
+
for (const arg of args) {
|
|
5077
|
+
if (arg === "mount") {
|
|
5078
|
+
foundMount = true;
|
|
5079
|
+
continue;
|
|
5080
|
+
}
|
|
5081
|
+
if (foundMount && arg.includes(":") && !arg.startsWith("-") && !arg.startsWith("/")) {
|
|
5082
|
+
return arg;
|
|
5083
|
+
}
|
|
5084
|
+
}
|
|
5085
|
+
return null;
|
|
5086
|
+
}
|
|
5087
|
+
function getRcloneArgs(pid) {
|
|
5088
|
+
try {
|
|
5089
|
+
if (OS === "linux") {
|
|
5090
|
+
const raw = readFileSync6(`/proc/${pid}/cmdline`);
|
|
5091
|
+
return new TextDecoder().decode(raw).split("\0").filter(Boolean);
|
|
5092
|
+
} else {
|
|
5093
|
+
const out = execSync(`ps -o args= -p ${pid}`, {
|
|
5094
|
+
encoding: "utf8",
|
|
5095
|
+
timeout: 2e3,
|
|
5096
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5097
|
+
}).trim();
|
|
5098
|
+
return out.split(/\s+/);
|
|
5099
|
+
}
|
|
5100
|
+
} catch {
|
|
5101
|
+
return [];
|
|
5102
|
+
}
|
|
5103
|
+
}
|
|
5104
|
+
function queryRcloneRC(rcAddr, fs) {
|
|
5105
|
+
try {
|
|
5106
|
+
const payload = JSON.stringify({ fs });
|
|
5107
|
+
const out = execSync(
|
|
5108
|
+
`curl -s --max-time 3 -X POST "http://${rcAddr}/vfs/stats" -H "Content-Type: application/json" -d '${payload}' 2>/dev/null`,
|
|
5109
|
+
{ encoding: "utf8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }
|
|
5110
|
+
);
|
|
5111
|
+
if (!out.trim()) return null;
|
|
5112
|
+
const data = JSON.parse(out);
|
|
5113
|
+
if (data.status && data.status >= 400) {
|
|
5114
|
+
return { error: data.error || `RC error (status ${data.status})`, erroredFiles: 0, uploadsInProgress: 0, uploadsQueued: 0, outOfSpace: false, bytesUsed: 0, files: 0, totalSize: "unknown" };
|
|
5115
|
+
}
|
|
5116
|
+
const dc = data.diskCache || {};
|
|
5117
|
+
return {
|
|
5118
|
+
erroredFiles: dc.erroredFiles ?? 0,
|
|
5119
|
+
uploadsInProgress: dc.uploadsInProgress ?? 0,
|
|
5120
|
+
uploadsQueued: dc.uploadsQueued ?? 0,
|
|
5121
|
+
outOfSpace: dc.outOfSpace ?? false,
|
|
5122
|
+
bytesUsed: dc.bytesUsed ?? 0,
|
|
5123
|
+
files: dc.files ?? 0,
|
|
5124
|
+
totalSize: data.totalSize || "unknown"
|
|
5125
|
+
};
|
|
5126
|
+
} catch {
|
|
5127
|
+
return { error: "RC endpoint unreachable", erroredFiles: 0, uploadsInProgress: 0, uploadsQueued: 0, outOfSpace: false, bytesUsed: 0, files: 0, totalSize: "unknown" };
|
|
5128
|
+
}
|
|
5129
|
+
}
|
|
5130
|
+
function detectFuseMount(vaultPath) {
|
|
4513
5131
|
try {
|
|
4514
5132
|
if (OS === "linux") {
|
|
4515
5133
|
const mounts = readFileSync6("/proc/mounts", "utf8");
|
|
@@ -4543,7 +5161,7 @@ function detectFuseMount(vaultPath) {
|
|
|
4543
5161
|
return null;
|
|
4544
5162
|
}
|
|
4545
5163
|
function writeTest(dir) {
|
|
4546
|
-
const testFile =
|
|
5164
|
+
const testFile = join28(dir, `.doctor-write-test-${process.pid}.tmp`);
|
|
4547
5165
|
const payload = `skillwiki doctor write test \u2014 ${Date.now()} \u2014 ${Math.random().toString(36).slice(2)}`;
|
|
4548
5166
|
const start = Date.now();
|
|
4549
5167
|
try {
|
|
@@ -4554,7 +5172,7 @@ function writeTest(dir) {
|
|
|
4554
5172
|
const writeMs = Date.now() - start;
|
|
4555
5173
|
const readStart = Date.now();
|
|
4556
5174
|
try {
|
|
4557
|
-
const back =
|
|
5175
|
+
const back = readFile20(testFile, "utf8");
|
|
4558
5176
|
const readMs = Date.now() - readStart;
|
|
4559
5177
|
if (back !== payload) {
|
|
4560
5178
|
try {
|
|
@@ -4643,12 +5261,12 @@ function detectCliChannels(argv, home) {
|
|
|
4643
5261
|
}
|
|
4644
5262
|
const plugin = findPlugin(home);
|
|
4645
5263
|
if (plugin) {
|
|
4646
|
-
const pluginBin =
|
|
5264
|
+
const pluginBin = join29(plugin.installPath, "bin", "skillwiki");
|
|
4647
5265
|
if (existsSync11(pluginBin)) {
|
|
4648
5266
|
channels.push({ name: "plugin", path: pluginBin, isDevLink: false });
|
|
4649
5267
|
}
|
|
4650
5268
|
}
|
|
4651
|
-
const installBin =
|
|
5269
|
+
const installBin = join29(home, ".claude", "skills", "bin", "skillwiki");
|
|
4652
5270
|
if (existsSync11(installBin)) {
|
|
4653
5271
|
channels.push({ name: "install", path: installBin, isDevLink: false });
|
|
4654
5272
|
}
|
|
@@ -4729,9 +5347,9 @@ function checkVaultStructure(resolvedPath) {
|
|
|
4729
5347
|
return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 vault directory does not exist");
|
|
4730
5348
|
}
|
|
4731
5349
|
const missing = [];
|
|
4732
|
-
if (!existsSync11(
|
|
5350
|
+
if (!existsSync11(join29(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
|
|
4733
5351
|
for (const dir of ["raw", "entities", "concepts", "meta"]) {
|
|
4734
|
-
if (!existsSync11(
|
|
5352
|
+
if (!existsSync11(join29(resolvedPath, dir))) missing.push(dir + "/");
|
|
4735
5353
|
}
|
|
4736
5354
|
if (missing.length === 0) {
|
|
4737
5355
|
return check("pass", "vault_structure", "Vault structure valid", "All required files and directories present");
|
|
@@ -4739,7 +5357,7 @@ function checkVaultStructure(resolvedPath) {
|
|
|
4739
5357
|
return check("warn", "vault_structure", "Vault structure valid", `Missing: ${missing.join(", ")} \u2014 run \`skillwiki init\` to add CodeWiki structure`);
|
|
4740
5358
|
}
|
|
4741
5359
|
function checkSkillsInstalled(home, cwd) {
|
|
4742
|
-
const srcDir = cwd ?
|
|
5360
|
+
const srcDir = cwd ? join29(cwd, "packages", "skills") : void 0;
|
|
4743
5361
|
if (srcDir && existsSync11(srcDir)) {
|
|
4744
5362
|
const found = findInstalledSkillMd(srcDir);
|
|
4745
5363
|
if (found.length > 0) {
|
|
@@ -4753,7 +5371,7 @@ function checkSkillsInstalled(home, cwd) {
|
|
|
4753
5371
|
return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (plugin v${plugin.version})`);
|
|
4754
5372
|
}
|
|
4755
5373
|
}
|
|
4756
|
-
const skillsDir =
|
|
5374
|
+
const skillsDir = join29(home, ".claude", "skills");
|
|
4757
5375
|
if (existsSync11(skillsDir)) {
|
|
4758
5376
|
const found = findInstalledSkillMd(skillsDir);
|
|
4759
5377
|
if (found.length > 0) {
|
|
@@ -4764,10 +5382,10 @@ function checkSkillsInstalled(home, cwd) {
|
|
|
4764
5382
|
}
|
|
4765
5383
|
function checkDuplicateSkills(home) {
|
|
4766
5384
|
const plugin = findPlugin(home);
|
|
4767
|
-
const skillsDir =
|
|
5385
|
+
const skillsDir = join29(home, ".claude", "skills");
|
|
4768
5386
|
const agentSkillDirs = [
|
|
4769
|
-
{ label: "~/.codex/skills/", path:
|
|
4770
|
-
{ label: "~/.agents/skills/", path:
|
|
5387
|
+
{ label: "~/.codex/skills/", path: join29(home, ".codex", "skills") },
|
|
5388
|
+
{ label: "~/.agents/skills/", path: join29(home, ".agents", "skills") }
|
|
4771
5389
|
];
|
|
4772
5390
|
if (!plugin) {
|
|
4773
5391
|
return check("pass", "skills_duplicate", "Skills not duplicated", "Single install channel");
|
|
@@ -4866,7 +5484,7 @@ async function checkProfiles(home) {
|
|
|
4866
5484
|
}
|
|
4867
5485
|
async function checkProjectLocalOverride(cwd) {
|
|
4868
5486
|
const dir = cwd ?? process.cwd();
|
|
4869
|
-
const envPath =
|
|
5487
|
+
const envPath = join29(dir, ".skillwiki", ".env");
|
|
4870
5488
|
if (existsSync11(envPath)) {
|
|
4871
5489
|
return check("pass", "project_local", "Project-local config", `Found: ${envPath}`);
|
|
4872
5490
|
}
|
|
@@ -4876,7 +5494,7 @@ function checkVaultGitRemote(resolvedPath) {
|
|
|
4876
5494
|
if (resolvedPath === void 0) {
|
|
4877
5495
|
return check("error", "vault_git_remote", "Vault git remote", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
4878
5496
|
}
|
|
4879
|
-
if (!existsSync11(
|
|
5497
|
+
if (!existsSync11(join29(resolvedPath, ".git"))) {
|
|
4880
5498
|
return check("warn", "vault_git_remote", "Vault git remote", "Vault is not a git repository \u2014 sync features unavailable");
|
|
4881
5499
|
}
|
|
4882
5500
|
try {
|
|
@@ -4899,9 +5517,9 @@ function checkObsidianTemplates(resolvedPath) {
|
|
|
4899
5517
|
return check("error", "obsidian_templates", "Obsidian templates", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
4900
5518
|
}
|
|
4901
5519
|
const missing = [];
|
|
4902
|
-
if (!existsSync11(
|
|
4903
|
-
if (!existsSync11(
|
|
4904
|
-
if (!existsSync11(
|
|
5520
|
+
if (!existsSync11(join29(resolvedPath, "_Templates"))) missing.push("_Templates/");
|
|
5521
|
+
if (!existsSync11(join29(resolvedPath, ".obsidian", "templates.json"))) missing.push(".obsidian/templates.json");
|
|
5522
|
+
if (!existsSync11(join29(resolvedPath, ".obsidian", "app.json"))) missing.push(".obsidian/app.json");
|
|
4905
5523
|
if (missing.length === 0) {
|
|
4906
5524
|
return check("pass", "obsidian_templates", "Obsidian templates", "Template folder and config present");
|
|
4907
5525
|
}
|
|
@@ -4911,7 +5529,7 @@ function checkDotStoreClean(resolvedPath) {
|
|
|
4911
5529
|
if (resolvedPath === void 0) {
|
|
4912
5530
|
return check("error", "dsstore_clean", "No .DS_Store in raw/", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
4913
5531
|
}
|
|
4914
|
-
const rawDir =
|
|
5532
|
+
const rawDir = join29(resolvedPath, "raw");
|
|
4915
5533
|
if (!existsSync11(rawDir)) {
|
|
4916
5534
|
return check("pass", "dsstore_clean", "No .DS_Store in raw/", "raw/ directory not found \u2014 check skipped");
|
|
4917
5535
|
}
|
|
@@ -4927,7 +5545,7 @@ function checkDotStoreClean(resolvedPath) {
|
|
|
4927
5545
|
if (entry.name === ".DS_Store") {
|
|
4928
5546
|
found.push(rel ? `${rel}/.DS_Store` : ".DS_Store");
|
|
4929
5547
|
} else if (entry.isDirectory()) {
|
|
4930
|
-
walk2(
|
|
5548
|
+
walk2(join29(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
|
|
4931
5549
|
}
|
|
4932
5550
|
}
|
|
4933
5551
|
})(rawDir, "");
|
|
@@ -4940,7 +5558,7 @@ function checkSyncLastPush(resolvedPath) {
|
|
|
4940
5558
|
if (resolvedPath === void 0) {
|
|
4941
5559
|
return check("error", "sync_last_push", "Vault sync recency", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
4942
5560
|
}
|
|
4943
|
-
if (!existsSync11(
|
|
5561
|
+
if (!existsSync11(join29(resolvedPath, ".git"))) {
|
|
4944
5562
|
return check("pass", "sync_last_push", "Vault sync recency", "No git repo \u2014 sync check skipped");
|
|
4945
5563
|
}
|
|
4946
5564
|
let timestamp;
|
|
@@ -4988,7 +5606,7 @@ function checkVaultGitDirty(resolvedPath) {
|
|
|
4988
5606
|
if (resolvedPath === void 0) {
|
|
4989
5607
|
return check("pass", "vault_git_dirty", "Vault git dirty state", "No vault path \u2014 check skipped");
|
|
4990
5608
|
}
|
|
4991
|
-
if (!existsSync11(
|
|
5609
|
+
if (!existsSync11(join29(resolvedPath, ".git"))) {
|
|
4992
5610
|
return check("pass", "vault_git_dirty", "Vault git dirty state", "No git repo \u2014 check skipped");
|
|
4993
5611
|
}
|
|
4994
5612
|
try {
|
|
@@ -5029,7 +5647,7 @@ function checkVaultGitComparison(resolvedPath, id, label, range, nonZeroSuffix,
|
|
|
5029
5647
|
if (resolvedPath === void 0) {
|
|
5030
5648
|
return check("pass", id, label, "No vault path \u2014 check skipped");
|
|
5031
5649
|
}
|
|
5032
|
-
if (!existsSync11(
|
|
5650
|
+
if (!existsSync11(join29(resolvedPath, ".git"))) {
|
|
5033
5651
|
return check("pass", id, label, "No git repo \u2014 check skipped");
|
|
5034
5652
|
}
|
|
5035
5653
|
if (!hasOriginMain(resolvedPath)) {
|
|
@@ -5049,13 +5667,38 @@ function checkVaultGitComparison(resolvedPath, id, label, range, nonZeroSuffix,
|
|
|
5049
5667
|
return check("warn", id, label, "Could not compare HEAD with origin/main");
|
|
5050
5668
|
}
|
|
5051
5669
|
}
|
|
5670
|
+
async function checkFleetIdentity(input) {
|
|
5671
|
+
if (!input.vaultPath) {
|
|
5672
|
+
return check("pass", "fleet_identity", "Fleet identity", "No vault path \u2014 check skipped");
|
|
5673
|
+
}
|
|
5674
|
+
const r = await runFleetContext({
|
|
5675
|
+
vault: input.vaultPath,
|
|
5676
|
+
env: { ...process.env, WIKI_PATH: input.envValue ?? input.vaultPath },
|
|
5677
|
+
home: input.home,
|
|
5678
|
+
cwd: input.cwd ?? process.cwd(),
|
|
5679
|
+
osHostname: process.env.HOSTNAME,
|
|
5680
|
+
user: process.env.USER
|
|
5681
|
+
});
|
|
5682
|
+
if (!r.result.ok) {
|
|
5683
|
+
return check("warn", "fleet_identity", "Fleet identity", "Could not evaluate fleet identity");
|
|
5684
|
+
}
|
|
5685
|
+
const data = r.result.data;
|
|
5686
|
+
if (!data.manifest_loaded) {
|
|
5687
|
+
return check("pass", "fleet_identity", "Fleet identity", "Fleet manifest unavailable \u2014 check skipped");
|
|
5688
|
+
}
|
|
5689
|
+
if (data.identity_status === "known") {
|
|
5690
|
+
return check("pass", "fleet_identity", "Fleet identity", `Resolved ${data.host_id ?? "unknown"} via ${data.source ?? "unknown"}`);
|
|
5691
|
+
}
|
|
5692
|
+
const detail = data.warnings.length > 0 ? data.warnings.join("; ") : "Fleet identity is unresolved";
|
|
5693
|
+
return check("warn", "fleet_identity", "Fleet identity", detail);
|
|
5694
|
+
}
|
|
5052
5695
|
function pullLogPaths(home) {
|
|
5053
5696
|
const paths = platform2() === "darwin" ? [
|
|
5054
|
-
|
|
5055
|
-
|
|
5697
|
+
join29(home, "Library", "Logs", "wiki-pull.log"),
|
|
5698
|
+
join29(home, ".local", "state", "vault-sync", "log", "wiki-pull.log")
|
|
5056
5699
|
] : [
|
|
5057
|
-
|
|
5058
|
-
|
|
5700
|
+
join29(home, ".local", "state", "vault-sync", "log", "wiki-pull.log"),
|
|
5701
|
+
join29(home, "Library", "Logs", "wiki-pull.log")
|
|
5059
5702
|
];
|
|
5060
5703
|
return [...new Set(paths)];
|
|
5061
5704
|
}
|
|
@@ -5095,7 +5738,7 @@ function checkS3MountPerf(resolvedPath) {
|
|
|
5095
5738
|
return check("pass", "s3_mount_perf", "S3 mount performance", "local disk");
|
|
5096
5739
|
}
|
|
5097
5740
|
const mountPoint = fuse.mountPoint;
|
|
5098
|
-
const conceptsDir =
|
|
5741
|
+
const conceptsDir = join29(resolvedPath, "concepts");
|
|
5099
5742
|
if (!existsSync11(conceptsDir)) {
|
|
5100
5743
|
return check("pass", "s3_mount_perf", "S3 mount performance", `S3 FUSE mount (${mountPoint}), no concepts/ to benchmark`);
|
|
5101
5744
|
}
|
|
@@ -5278,7 +5921,7 @@ function checkWriteTest(resolvedPath) {
|
|
|
5278
5921
|
if (!fuse) {
|
|
5279
5922
|
return check("pass", "s3_write_test", "S3 write test", "local disk \u2014 check skipped");
|
|
5280
5923
|
}
|
|
5281
|
-
const conceptsDir =
|
|
5924
|
+
const conceptsDir = join29(resolvedPath, "concepts");
|
|
5282
5925
|
if (!existsSync11(conceptsDir)) {
|
|
5283
5926
|
return check("pass", "s3_write_test", "S3 write test", "no concepts/ dir to test \u2014 check skipped");
|
|
5284
5927
|
}
|
|
@@ -5365,7 +6008,7 @@ function checkVfsCacheHealth(resolvedPath) {
|
|
|
5365
6008
|
}
|
|
5366
6009
|
function readVaultSyncConfig(home) {
|
|
5367
6010
|
try {
|
|
5368
|
-
const content = readFileSync7(
|
|
6011
|
+
const content = readFileSync7(join29(home, ".skillwiki", ".env"), "utf8");
|
|
5369
6012
|
let installed = false;
|
|
5370
6013
|
let role;
|
|
5371
6014
|
for (const line of content.split(/\r?\n/)) {
|
|
@@ -5399,11 +6042,11 @@ function vaultSyncChecks(input) {
|
|
|
5399
6042
|
];
|
|
5400
6043
|
}
|
|
5401
6044
|
const isMac = os === "darwin";
|
|
5402
|
-
const logDir = input.logDir ?? (isMac ?
|
|
5403
|
-
const shareDir = input.shareDir ?? (isMac ?
|
|
5404
|
-
const filterPath = input.filterPath ??
|
|
6045
|
+
const logDir = input.logDir ?? (isMac ? join29(home, "Library", "Logs") : join29(home, ".local", "state", "vault-sync", "log"));
|
|
6046
|
+
const shareDir = input.shareDir ?? (isMac ? join29(home, "Library", "Application Support", "vault-sync", "bin") : join29(home, ".local", "share", "vault-sync", "bin"));
|
|
6047
|
+
const filterPath = input.filterPath ?? join29(home, ".config", "rclone", "wiki-push-filters.txt");
|
|
5405
6048
|
const snapshotPath = input.snapshotScriptPath ?? "/root/.hermes/scripts/wiki-snapshot-v3.sh";
|
|
5406
|
-
const pushScriptPath =
|
|
6049
|
+
const pushScriptPath = join29(shareDir, "wiki-push.sh");
|
|
5407
6050
|
const c1 = existsSync11(pushScriptPath) ? check("pass", "vault_sync_installed", "Vault sync installed", `Found: ${pushScriptPath}`) : check("error", "vault_sync_installed", "Vault sync installed", `Script not found at ${pushScriptPath} \u2014 run vault-sync-install`);
|
|
5408
6051
|
let c2;
|
|
5409
6052
|
try {
|
|
@@ -5455,7 +6098,7 @@ function vaultSyncChecks(input) {
|
|
|
5455
6098
|
"Scheduler check failed \u2014 run vault-sync-install"
|
|
5456
6099
|
);
|
|
5457
6100
|
}
|
|
5458
|
-
const logFile =
|
|
6101
|
+
const logFile = join29(logDir, "wiki-push.log");
|
|
5459
6102
|
let c3;
|
|
5460
6103
|
try {
|
|
5461
6104
|
const logContent = readFileSync7(logFile, "utf8");
|
|
@@ -5526,7 +6169,7 @@ function vaultSyncChecks(input) {
|
|
|
5526
6169
|
`Log directory not found at ${logDir}`
|
|
5527
6170
|
);
|
|
5528
6171
|
}
|
|
5529
|
-
const fetchLogFile =
|
|
6172
|
+
const fetchLogFile = join29(logDir, "wiki-fetch.log");
|
|
5530
6173
|
let cFetch;
|
|
5531
6174
|
try {
|
|
5532
6175
|
const logContent = readFileSync7(fetchLogFile, "utf8");
|
|
@@ -5668,15 +6311,15 @@ function findSkillMd(dir) {
|
|
|
5668
6311
|
}
|
|
5669
6312
|
for (const entry of entries) {
|
|
5670
6313
|
if (entry.isFile() && entry.name === "SKILL.md") {
|
|
5671
|
-
results.push(
|
|
6314
|
+
results.push(join29(dir, entry.name));
|
|
5672
6315
|
} else if (entry.isDirectory()) {
|
|
5673
|
-
results.push(...findSkillMd(
|
|
6316
|
+
results.push(...findSkillMd(join29(dir, entry.name)));
|
|
5674
6317
|
}
|
|
5675
6318
|
}
|
|
5676
6319
|
return results;
|
|
5677
6320
|
}
|
|
5678
6321
|
function findInstalledSkillMd(dir) {
|
|
5679
|
-
const directSkills = findSkillNames(dir).map((name) =>
|
|
6322
|
+
const directSkills = findSkillNames(dir).map((name) => join29(dir, name, "SKILL.md"));
|
|
5680
6323
|
return directSkills.length > 0 ? directSkills : findSkillMd(dir);
|
|
5681
6324
|
}
|
|
5682
6325
|
function findSkillNames(dir) {
|
|
@@ -5688,7 +6331,7 @@ function findSkillNames(dir) {
|
|
|
5688
6331
|
return results;
|
|
5689
6332
|
}
|
|
5690
6333
|
for (const entry of entries) {
|
|
5691
|
-
if (entry.isDirectory() && existsSync11(
|
|
6334
|
+
if (entry.isDirectory() && existsSync11(join29(dir, entry.name, "SKILL.md"))) {
|
|
5692
6335
|
results.push(entry.name);
|
|
5693
6336
|
}
|
|
5694
6337
|
}
|
|
@@ -5732,7 +6375,7 @@ async function vaultMetrics(resolvedPath) {
|
|
|
5732
6375
|
}
|
|
5733
6376
|
let logLines = 0;
|
|
5734
6377
|
try {
|
|
5735
|
-
logLines = readFileSync7(
|
|
6378
|
+
logLines = readFileSync7(join29(resolvedPath, "log.md"), "utf8").split("\n").length;
|
|
5736
6379
|
} catch {
|
|
5737
6380
|
}
|
|
5738
6381
|
return [
|
|
@@ -5762,6 +6405,12 @@ async function runDoctor(input) {
|
|
|
5762
6405
|
checks.push(checkVaultStructure(resolvedPath));
|
|
5763
6406
|
checks.push(checkObsidianTemplates(resolvedPath));
|
|
5764
6407
|
checks.push(checkVaultGitRemote(resolvedPath));
|
|
6408
|
+
checks.push(await checkFleetIdentity({
|
|
6409
|
+
vaultPath: resolvedPath,
|
|
6410
|
+
home: input.home,
|
|
6411
|
+
cwd: input.cwd,
|
|
6412
|
+
envValue: input.envValue
|
|
6413
|
+
}));
|
|
5765
6414
|
checks.push(checkSyncLastPush(resolvedPath));
|
|
5766
6415
|
checks.push(checkVaultGitDirty(resolvedPath));
|
|
5767
6416
|
checks.push(checkVaultGitAhead(resolvedPath));
|
|
@@ -5935,27 +6584,27 @@ function runVaultSyncHealth(home, syncMode) {
|
|
|
5935
6584
|
};
|
|
5936
6585
|
}
|
|
5937
6586
|
const isMac = platform3() === "darwin";
|
|
5938
|
-
const shareDir = isMac ?
|
|
5939
|
-
const logDir = isMac ?
|
|
5940
|
-
const filterPath =
|
|
6587
|
+
const shareDir = isMac ? join30(home, "Library", "Application Support", "vault-sync", "bin") : join30(home, ".local", "share", "vault-sync", "bin");
|
|
6588
|
+
const logDir = isMac ? join30(home, "Library", "Logs") : join30(home, ".local", "state", "vault-sync", "log");
|
|
6589
|
+
const filterPath = join30(home, ".config", "rclone", "wiki-push-filters.txt");
|
|
5941
6590
|
const checks = [];
|
|
5942
|
-
const pushScript =
|
|
6591
|
+
const pushScript = join30(shareDir, "wiki-push.sh");
|
|
5943
6592
|
checks.push(existsSync12(pushScript) ? { id: "vault_sync_installed", label: "Vault sync installed", status: "pass", detail: `Found: ${pushScript}` } : { id: "vault_sync_installed", label: "Vault sync installed", status: "error", detail: `Script missing: ${pushScript}` });
|
|
5944
6593
|
if (isMac) {
|
|
5945
|
-
const pushPlist =
|
|
5946
|
-
const fetchPlist =
|
|
6594
|
+
const pushPlist = join30(home, "Library", "LaunchAgents", "com.karlchow.wiki-push.plist");
|
|
6595
|
+
const fetchPlist = join30(home, "Library", "LaunchAgents", "com.karlchow.wiki-fetch.plist");
|
|
5947
6596
|
checks.push(existsSync12(pushPlist) && existsSync12(fetchPlist) ? { id: "vault_sync_jobs_enabled", label: "Vault sync jobs enabled", status: "pass", detail: "launchd unit files present (read-only mode)" } : { id: "vault_sync_jobs_enabled", label: "Vault sync jobs enabled", status: "warn", detail: "launchd unit files missing (read-only mode)" });
|
|
5948
6597
|
checks.push({ id: "vault_sync_fuse_refresh_job", label: "Vault sync fuse refresh job", status: "pass", detail: "macOS host \u2014 check skipped" });
|
|
5949
6598
|
} else {
|
|
5950
|
-
const pushTimer =
|
|
5951
|
-
const fetchTimer =
|
|
5952
|
-
const fuseTimer =
|
|
5953
|
-
const fuseService =
|
|
6599
|
+
const pushTimer = join30(home, ".config", "systemd", "user", "wiki-push.timer");
|
|
6600
|
+
const fetchTimer = join30(home, ".config", "systemd", "user", "wiki-fetch.timer");
|
|
6601
|
+
const fuseTimer = join30(home, ".config", "systemd", "user", "wiki-fuse-refresh.timer");
|
|
6602
|
+
const fuseService = join30(home, ".config", "systemd", "user", "wiki-fuse-refresh.service");
|
|
5954
6603
|
checks.push(existsSync12(pushTimer) && existsSync12(fetchTimer) ? { id: "vault_sync_jobs_enabled", label: "Vault sync jobs enabled", status: "pass", detail: "systemd timer unit files present (read-only mode)" } : { id: "vault_sync_jobs_enabled", label: "Vault sync jobs enabled", status: "warn", detail: "systemd timer unit files missing (read-only mode)" });
|
|
5955
6604
|
checks.push(existsSync12(fuseTimer) && existsSync12(fuseService) ? { id: "vault_sync_fuse_refresh_job", label: "Vault sync fuse refresh job", status: "pass", detail: "wiki-fuse-refresh unit files present (read-only mode)" } : { id: "vault_sync_fuse_refresh_job", label: "Vault sync fuse refresh job", status: "warn", detail: "wiki-fuse-refresh unit files missing (read-only mode)" });
|
|
5956
6605
|
}
|
|
5957
|
-
checks.push(classifyLog(
|
|
5958
|
-
checks.push(classifyLog(
|
|
6606
|
+
checks.push(classifyLog(join30(logDir, "wiki-push.log"), "vault_sync_last_push_age", "Vault sync last push recency", /OK push/));
|
|
6607
|
+
checks.push(classifyLog(join30(logDir, "wiki-fetch.log"), "vault_sync_last_fetch_status", "Vault sync last fetch status", /NOTIFY|OK behind|OK/));
|
|
5959
6608
|
if (!existsSync12(filterPath)) {
|
|
5960
6609
|
checks.push({ id: "vault_sync_filter_present", label: "Vault sync filter file present", status: "error", detail: `Filter missing: ${filterPath}` });
|
|
5961
6610
|
} else {
|
|
@@ -6181,8 +6830,8 @@ async function runHealth(input) {
|
|
|
6181
6830
|
}
|
|
6182
6831
|
|
|
6183
6832
|
// src/commands/archive.ts
|
|
6184
|
-
import { rename as rename7, mkdir as mkdir9, readFile as
|
|
6185
|
-
import { join as
|
|
6833
|
+
import { rename as rename7, mkdir as mkdir9, readFile as readFile21, writeFile as writeFile10 } from "fs/promises";
|
|
6834
|
+
import { join as join31, dirname as dirname11 } from "path";
|
|
6186
6835
|
function countWikilinks(body, slug) {
|
|
6187
6836
|
const escaped = slug.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6188
6837
|
const re = new RegExp(`\\[\\[${escaped}(?:[|#][^\\]]*)?\\]\\]`, "g");
|
|
@@ -6210,7 +6859,7 @@ async function runArchive(input) {
|
|
|
6210
6859
|
if (!relPath) return { exitCode: ExitCode.ARCHIVE_TARGET_NOT_FOUND, result: err("ARCHIVE_TARGET_NOT_FOUND", { page: input.page }) };
|
|
6211
6860
|
if (relPath.startsWith("_archive/")) return { exitCode: ExitCode.ARCHIVE_ALREADY_ARCHIVED, result: err("ARCHIVE_ALREADY_ARCHIVED", { page: relPath }) };
|
|
6212
6861
|
const slug = relPath.replace(/\.md$/, "").split("/").pop();
|
|
6213
|
-
const archivePath =
|
|
6862
|
+
const archivePath = join31("_archive", relPath).replace(/\\/g, "/");
|
|
6214
6863
|
let cascade;
|
|
6215
6864
|
if (input.cascade) {
|
|
6216
6865
|
const wikilinkRefs = [];
|
|
@@ -6234,7 +6883,7 @@ async function runArchive(input) {
|
|
|
6234
6883
|
const indexRefs = [];
|
|
6235
6884
|
if (!isRaw) {
|
|
6236
6885
|
try {
|
|
6237
|
-
const idx = await
|
|
6886
|
+
const idx = await readFile21(join31(input.vault, "index.md"), "utf8");
|
|
6238
6887
|
idx.split("\n").forEach((line, i) => {
|
|
6239
6888
|
if (line.includes(`[[${slug}]]`)) indexRefs.push({ line: i + 1, text: line });
|
|
6240
6889
|
});
|
|
@@ -6260,8 +6909,8 @@ async function runArchive(input) {
|
|
|
6260
6909
|
}
|
|
6261
6910
|
if (input.cascade && input.apply && cascade) {
|
|
6262
6911
|
for (const ref of cascade.source_array_refs) {
|
|
6263
|
-
const absPath =
|
|
6264
|
-
const text = await
|
|
6912
|
+
const absPath = join31(input.vault, ref.page);
|
|
6913
|
+
const text = await readFile21(absPath, "utf8");
|
|
6265
6914
|
const split = splitFrontmatter(text);
|
|
6266
6915
|
if (!split.ok) continue;
|
|
6267
6916
|
const before = split.data.rawFrontmatter;
|
|
@@ -6278,12 +6927,12 @@ ${fmRewritten}
|
|
|
6278
6927
|
}
|
|
6279
6928
|
}
|
|
6280
6929
|
}
|
|
6281
|
-
await mkdir9(dirname11(
|
|
6930
|
+
await mkdir9(dirname11(join31(input.vault, archivePath)), { recursive: true });
|
|
6282
6931
|
let indexUpdated = false;
|
|
6283
6932
|
if (!isRaw) {
|
|
6284
|
-
const indexPath =
|
|
6933
|
+
const indexPath = join31(input.vault, "index.md");
|
|
6285
6934
|
try {
|
|
6286
|
-
const idx = await
|
|
6935
|
+
const idx = await readFile21(indexPath, "utf8");
|
|
6287
6936
|
const originalLines = idx.split("\n");
|
|
6288
6937
|
const filtered = originalLines.filter((l) => !l.includes(`[[${slug}]]`));
|
|
6289
6938
|
if (filtered.length !== originalLines.length) {
|
|
@@ -6294,7 +6943,7 @@ ${fmRewritten}
|
|
|
6294
6943
|
if (e instanceof Error && "code" in e && e.code !== "ENOENT") throw e;
|
|
6295
6944
|
}
|
|
6296
6945
|
}
|
|
6297
|
-
await rename7(
|
|
6946
|
+
await rename7(join31(input.vault, relPath), join31(input.vault, archivePath));
|
|
6298
6947
|
appendLastOp(input.vault, {
|
|
6299
6948
|
operation: input.cascade ? "archive-cascade" : "archive",
|
|
6300
6949
|
summary: `moved ${relPath} to ${archivePath}${input.cascade ? ` (cascade: ${cascade?.source_array_refs.length ?? 0} source arrays updated)` : ""}`,
|
|
@@ -6317,7 +6966,7 @@ ${fmRewritten}
|
|
|
6317
6966
|
}
|
|
6318
6967
|
|
|
6319
6968
|
// src/commands/drift.ts
|
|
6320
|
-
import { createHash as
|
|
6969
|
+
import { createHash as createHash5 } from "crypto";
|
|
6321
6970
|
|
|
6322
6971
|
// src/utils/fetch.ts
|
|
6323
6972
|
async function controlledFetch(url, opts) {
|
|
@@ -6396,7 +7045,7 @@ async function runDrift(input) {
|
|
|
6396
7045
|
});
|
|
6397
7046
|
continue;
|
|
6398
7047
|
}
|
|
6399
|
-
const currentHash =
|
|
7048
|
+
const currentHash = createHash5("sha256").update(Buffer.from(resp.data.body, "utf8")).digest("hex");
|
|
6400
7049
|
const identity = assessSourceIdentity({
|
|
6401
7050
|
rawPath: raw.relPath,
|
|
6402
7051
|
sourceUrl,
|
|
@@ -6703,7 +7352,7 @@ ${newBody}`;
|
|
|
6703
7352
|
|
|
6704
7353
|
// src/commands/update.ts
|
|
6705
7354
|
import { execSync as execSync3 } from "child_process";
|
|
6706
|
-
import { join as
|
|
7355
|
+
import { join as join32 } from "path";
|
|
6707
7356
|
|
|
6708
7357
|
// src/utils/package-info.ts
|
|
6709
7358
|
import { readFileSync as readFileSync9 } from "fs";
|
|
@@ -6733,7 +7382,7 @@ function resolveGlobalSkillsRoot() {
|
|
|
6733
7382
|
encoding: "utf8",
|
|
6734
7383
|
timeout: 5e3
|
|
6735
7384
|
}).trim();
|
|
6736
|
-
return
|
|
7385
|
+
return join32(globalRoot, "skillwiki", "skills");
|
|
6737
7386
|
} catch {
|
|
6738
7387
|
return null;
|
|
6739
7388
|
}
|
|
@@ -6757,7 +7406,7 @@ async function runUpdate(input) {
|
|
|
6757
7406
|
const pkg2 = readCliPackageJson();
|
|
6758
7407
|
const currentVersion = pkg2.version;
|
|
6759
7408
|
const tag = normalizeDistTag(input.distTag);
|
|
6760
|
-
const target =
|
|
7409
|
+
const target = join32(input.home, ".claude", "skills");
|
|
6761
7410
|
let latest;
|
|
6762
7411
|
try {
|
|
6763
7412
|
latest = execSync3(`npm view skillwiki@${tag} version`, {
|
|
@@ -6829,13 +7478,13 @@ async function runUpdate(input) {
|
|
|
6829
7478
|
// src/commands/self-update.ts
|
|
6830
7479
|
import { execSync as execSync4 } from "child_process";
|
|
6831
7480
|
import { existsSync as existsSync13, readFileSync as readFileSync10 } from "fs";
|
|
6832
|
-
import { join as
|
|
7481
|
+
import { join as join33 } from "path";
|
|
6833
7482
|
var DEFAULT_SOURCE_ROOT_SUFFIX = "/Desktop/code/llm-wiki";
|
|
6834
7483
|
async function runSelfUpdate(input) {
|
|
6835
7484
|
const currentVersion = readCliPackageJson().version;
|
|
6836
7485
|
const sourceRoot = input.sourceRoot ?? `${input.home}${DEFAULT_SOURCE_ROOT_SUFFIX}`;
|
|
6837
7486
|
const distTag = normalizeDistTag(input.distTag);
|
|
6838
|
-
const localPkgPath =
|
|
7487
|
+
const localPkgPath = join33(sourceRoot, "packages", "cli", "package.json");
|
|
6839
7488
|
const hasLocalSource = existsSync13(localPkgPath);
|
|
6840
7489
|
if (input.check) {
|
|
6841
7490
|
let availableVersion = null;
|
|
@@ -6967,10 +7616,10 @@ async function runSelfUpdate(input) {
|
|
|
6967
7616
|
}
|
|
6968
7617
|
|
|
6969
7618
|
// src/commands/transcripts.ts
|
|
6970
|
-
import { readdir as readdir5, stat as stat8, readFile as
|
|
6971
|
-
import { join as
|
|
7619
|
+
import { readdir as readdir5, stat as stat8, readFile as readFile22 } from "fs/promises";
|
|
7620
|
+
import { join as join34 } from "path";
|
|
6972
7621
|
async function runTranscripts(input) {
|
|
6973
|
-
const dir =
|
|
7622
|
+
const dir = join34(input.vault, "raw", "transcripts");
|
|
6974
7623
|
let entries;
|
|
6975
7624
|
try {
|
|
6976
7625
|
entries = await readdir5(dir, { withFileTypes: true });
|
|
@@ -6980,8 +7629,8 @@ async function runTranscripts(input) {
|
|
|
6980
7629
|
const transcripts = [];
|
|
6981
7630
|
for (const entry of entries) {
|
|
6982
7631
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
6983
|
-
const filePath =
|
|
6984
|
-
const content = await
|
|
7632
|
+
const filePath = join34(dir, entry.name);
|
|
7633
|
+
const content = await readFile22(filePath, "utf8");
|
|
6985
7634
|
const fm = extractFrontmatter(content);
|
|
6986
7635
|
if (!fm.ok) continue;
|
|
6987
7636
|
const ingested = typeof fm.data.ingested === "string" ? fm.data.ingested : "";
|
|
@@ -6998,12 +7647,12 @@ async function runTranscripts(input) {
|
|
|
6998
7647
|
}
|
|
6999
7648
|
|
|
7000
7649
|
// src/commands/project-index.ts
|
|
7001
|
-
import { readdir as readdir6, readFile as
|
|
7002
|
-
import { join as
|
|
7650
|
+
import { readdir as readdir6, readFile as readFile23, writeFile as writeFile11, mkdir as mkdir10 } from "fs/promises";
|
|
7651
|
+
import { join as join35, dirname as dirname12 } from "path";
|
|
7003
7652
|
var LAYER2_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
|
|
7004
7653
|
async function runProjectIndex(input) {
|
|
7005
7654
|
const slug = input.slug;
|
|
7006
|
-
const projectDir =
|
|
7655
|
+
const projectDir = join35(input.vault, "projects", slug);
|
|
7007
7656
|
try {
|
|
7008
7657
|
await readdir6(projectDir);
|
|
7009
7658
|
} catch {
|
|
@@ -7014,15 +7663,15 @@ async function runProjectIndex(input) {
|
|
|
7014
7663
|
}
|
|
7015
7664
|
const wikilinkPattern = `[[${slug}]]`;
|
|
7016
7665
|
const entries = [];
|
|
7017
|
-
const compoundDir =
|
|
7666
|
+
const compoundDir = join35(input.vault, "projects", slug, "compound");
|
|
7018
7667
|
try {
|
|
7019
7668
|
const compoundFiles = await readdir6(compoundDir, { withFileTypes: true });
|
|
7020
7669
|
for (const entry of compoundFiles) {
|
|
7021
7670
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
7022
|
-
const filePath =
|
|
7671
|
+
const filePath = join35(compoundDir, entry.name);
|
|
7023
7672
|
let text;
|
|
7024
7673
|
try {
|
|
7025
|
-
text = await
|
|
7674
|
+
text = await readFile23(filePath, "utf8");
|
|
7026
7675
|
} catch {
|
|
7027
7676
|
continue;
|
|
7028
7677
|
}
|
|
@@ -7039,16 +7688,16 @@ async function runProjectIndex(input) {
|
|
|
7039
7688
|
for (const dir of LAYER2_DIRS) {
|
|
7040
7689
|
let files;
|
|
7041
7690
|
try {
|
|
7042
|
-
files = await readdir6(
|
|
7691
|
+
files = await readdir6(join35(input.vault, dir), { withFileTypes: true });
|
|
7043
7692
|
} catch {
|
|
7044
7693
|
continue;
|
|
7045
7694
|
}
|
|
7046
7695
|
for (const entry of files) {
|
|
7047
7696
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
7048
|
-
const filePath =
|
|
7697
|
+
const filePath = join35(input.vault, dir, entry.name);
|
|
7049
7698
|
let text;
|
|
7050
7699
|
try {
|
|
7051
|
-
text = await
|
|
7700
|
+
text = await readFile23(filePath, "utf8");
|
|
7052
7701
|
} catch {
|
|
7053
7702
|
continue;
|
|
7054
7703
|
}
|
|
@@ -7069,11 +7718,11 @@ async function runProjectIndex(input) {
|
|
|
7069
7718
|
const tb = typeOrder[b.type] ?? 99;
|
|
7070
7719
|
return ta !== tb ? ta - tb : a.title.localeCompare(b.title);
|
|
7071
7720
|
});
|
|
7072
|
-
const indexPath =
|
|
7721
|
+
const indexPath = join35(projectDir, "knowledge.md");
|
|
7073
7722
|
let existing = false;
|
|
7074
7723
|
let stale = false;
|
|
7075
7724
|
try {
|
|
7076
|
-
const existingText = await
|
|
7725
|
+
const existingText = await readFile23(indexPath, "utf8");
|
|
7077
7726
|
existing = true;
|
|
7078
7727
|
const existingEntries = existingText.split("\n").filter((l) => l.startsWith("- [["));
|
|
7079
7728
|
const existingPages = new Set(existingEntries.map((l) => {
|
|
@@ -7143,9 +7792,9 @@ ${entries.map((e) => ` ${e.type}: [[${e.page.replace(/\.md$/, "")}]] \u2014 ${e
|
|
|
7143
7792
|
|
|
7144
7793
|
// src/commands/compound.ts
|
|
7145
7794
|
import { writeFile as writeFile12, mkdir as mkdir11, readdir as readdir7, unlink as unlink4 } from "fs/promises";
|
|
7146
|
-
import { join as
|
|
7795
|
+
import { join as join36 } from "path";
|
|
7147
7796
|
import { existsSync as existsSync14 } from "fs";
|
|
7148
|
-
import { readFile as
|
|
7797
|
+
import { readFile as readFile24 } from "fs/promises";
|
|
7149
7798
|
var RETRO_HEADING_RE = /^## \[(\d{4}-\d{2}-\d{2})(?:\s+[^\]]+)?\] retro \| loop cycle(?: (\d+))?: (.+)$/;
|
|
7150
7799
|
var FIELD_RE = {
|
|
7151
7800
|
improve: /^-\s+\*?\*?Improve:?\*?\*?\s*(.+)$/m,
|
|
@@ -7243,17 +7892,17 @@ function extractRetroFields(date, cycleName, block) {
|
|
|
7243
7892
|
};
|
|
7244
7893
|
}
|
|
7245
7894
|
async function runCompound(input) {
|
|
7246
|
-
const logPath =
|
|
7895
|
+
const logPath = join36(input.vault, "log.md");
|
|
7247
7896
|
let logText;
|
|
7248
7897
|
try {
|
|
7249
|
-
logText = await
|
|
7898
|
+
logText = await readFile24(logPath, "utf8");
|
|
7250
7899
|
} catch {
|
|
7251
7900
|
return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
|
|
7252
7901
|
}
|
|
7253
7902
|
const entries = parseRetroEntries(logText);
|
|
7254
7903
|
const promoted = [];
|
|
7255
7904
|
const skipped = [];
|
|
7256
|
-
const compoundDir =
|
|
7905
|
+
const compoundDir = join36(input.vault, "projects", input.project, "compound");
|
|
7257
7906
|
for (const entry of entries) {
|
|
7258
7907
|
const generalizeValue = entry.generalize.trim();
|
|
7259
7908
|
if (!/^yes/i.test(generalizeValue)) {
|
|
@@ -7261,7 +7910,7 @@ async function runCompound(input) {
|
|
|
7261
7910
|
continue;
|
|
7262
7911
|
}
|
|
7263
7912
|
const slug = slugify(entry.cycleName);
|
|
7264
|
-
const compoundPath =
|
|
7913
|
+
const compoundPath = join36(compoundDir, `${slug}.md`);
|
|
7265
7914
|
if (existsSync14(compoundPath)) {
|
|
7266
7915
|
skipped.push(entry.date);
|
|
7267
7916
|
continue;
|
|
@@ -7322,7 +7971,7 @@ async function runCompound(input) {
|
|
|
7322
7971
|
};
|
|
7323
7972
|
}
|
|
7324
7973
|
async function runCompoundDelete(input) {
|
|
7325
|
-
const projectDir =
|
|
7974
|
+
const projectDir = join36(input.vault, "projects", input.project);
|
|
7326
7975
|
if (!existsSync14(projectDir)) {
|
|
7327
7976
|
return {
|
|
7328
7977
|
exitCode: ExitCode.PROJECT_NOT_FOUND,
|
|
@@ -7330,7 +7979,7 @@ async function runCompoundDelete(input) {
|
|
|
7330
7979
|
};
|
|
7331
7980
|
}
|
|
7332
7981
|
const entryName = input.entry.replace(/\.md$/, "");
|
|
7333
|
-
const compoundPath =
|
|
7982
|
+
const compoundPath = join36(projectDir, "compound", `${entryName}.md`);
|
|
7334
7983
|
if (!existsSync14(compoundPath)) {
|
|
7335
7984
|
return {
|
|
7336
7985
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -7364,7 +8013,7 @@ knowledge.md regenerated`
|
|
|
7364
8013
|
};
|
|
7365
8014
|
}
|
|
7366
8015
|
async function runCompoundList(input) {
|
|
7367
|
-
const compoundDir =
|
|
8016
|
+
const compoundDir = join36(input.vault, "projects", input.project, "compound");
|
|
7368
8017
|
if (!existsSync14(compoundDir)) {
|
|
7369
8018
|
return {
|
|
7370
8019
|
exitCode: ExitCode.OK,
|
|
@@ -7395,10 +8044,10 @@ could not read compound directory`
|
|
|
7395
8044
|
const entries = [];
|
|
7396
8045
|
for (const dirent of dirents) {
|
|
7397
8046
|
if (!dirent.isFile() || !dirent.name.endsWith(".md")) continue;
|
|
7398
|
-
const filePath =
|
|
8047
|
+
const filePath = join36(compoundDir, dirent.name);
|
|
7399
8048
|
let text;
|
|
7400
8049
|
try {
|
|
7401
|
-
text = await
|
|
8050
|
+
text = await readFile24(filePath, "utf8");
|
|
7402
8051
|
} catch {
|
|
7403
8052
|
continue;
|
|
7404
8053
|
}
|
|
@@ -7429,8 +8078,8 @@ no compound entries found`;
|
|
|
7429
8078
|
// src/commands/observe.ts
|
|
7430
8079
|
import { mkdir as mkdir12, writeFile as writeFile13 } from "fs/promises";
|
|
7431
8080
|
import { existsSync as existsSync15, statSync as statSync4 } from "fs";
|
|
7432
|
-
import { join as
|
|
7433
|
-
import { createHash as
|
|
8081
|
+
import { join as join37 } from "path";
|
|
8082
|
+
import { createHash as createHash6 } from "crypto";
|
|
7434
8083
|
var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
|
|
7435
8084
|
function slugify2(text) {
|
|
7436
8085
|
const words = text.trim().split(/\s+/).slice(0, 6).join("-").toLowerCase().replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
@@ -7458,7 +8107,7 @@ async function runObserve(input) {
|
|
|
7458
8107
|
result: err("VAULT_PATH_INVALID", { path: input.vault })
|
|
7459
8108
|
};
|
|
7460
8109
|
}
|
|
7461
|
-
const transcriptsDir =
|
|
8110
|
+
const transcriptsDir = join37(input.vault, "raw", "transcripts");
|
|
7462
8111
|
try {
|
|
7463
8112
|
await mkdir12(transcriptsDir, { recursive: true });
|
|
7464
8113
|
} catch {
|
|
@@ -7470,11 +8119,11 @@ async function runObserve(input) {
|
|
|
7470
8119
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
7471
8120
|
const slug = slugify2(input.text);
|
|
7472
8121
|
const fileName = `${today}-observation-${slug}.md`;
|
|
7473
|
-
const filePath =
|
|
8122
|
+
const filePath = join37(transcriptsDir, fileName);
|
|
7474
8123
|
const body = `
|
|
7475
8124
|
${input.text.trim()}
|
|
7476
8125
|
`;
|
|
7477
|
-
const sha256 =
|
|
8126
|
+
const sha256 = createHash6("sha256").update(Buffer.from(body, "utf8")).digest("hex");
|
|
7478
8127
|
const frontmatterLines = [
|
|
7479
8128
|
"---",
|
|
7480
8129
|
"source_url:",
|
|
@@ -7510,8 +8159,8 @@ ${input.text.trim()}
|
|
|
7510
8159
|
}
|
|
7511
8160
|
|
|
7512
8161
|
// src/commands/session-brief.ts
|
|
7513
|
-
import { mkdir as mkdir13, readFile as
|
|
7514
|
-
import { join as
|
|
8162
|
+
import { mkdir as mkdir13, readFile as readFile25, writeFile as writeFile14 } from "fs/promises";
|
|
8163
|
+
import { join as join38, relative as relative3, sep as sep3 } from "path";
|
|
7515
8164
|
var MAX_WORDS = 900;
|
|
7516
8165
|
async function runSessionBrief(input) {
|
|
7517
8166
|
const scan = await scanVault(input.vault);
|
|
@@ -7589,7 +8238,7 @@ async function resolveProject(input) {
|
|
|
7589
8238
|
const envProject = input.env?.SKILLWIKI_PROJECT;
|
|
7590
8239
|
if (envProject) return envProject;
|
|
7591
8240
|
const cwd = input.cwd ?? process.cwd();
|
|
7592
|
-
const projectDotenv = await readProjectSlug(
|
|
8241
|
+
const projectDotenv = await readProjectSlug(join38(cwd, ".skillwiki", ".env"));
|
|
7593
8242
|
if (projectDotenv) return projectDotenv;
|
|
7594
8243
|
const inferred = inferProjectFromPath(input.vault, cwd);
|
|
7595
8244
|
if (inferred) return inferred;
|
|
@@ -7598,7 +8247,7 @@ async function resolveProject(input) {
|
|
|
7598
8247
|
async function readProjectSlug(file) {
|
|
7599
8248
|
let text;
|
|
7600
8249
|
try {
|
|
7601
|
-
text = await
|
|
8250
|
+
text = await readFile25(file, "utf8");
|
|
7602
8251
|
} catch {
|
|
7603
8252
|
return void 0;
|
|
7604
8253
|
}
|
|
@@ -7723,7 +8372,7 @@ function appendTextSection(lines, title, items, empty) {
|
|
|
7723
8372
|
lines.push("");
|
|
7724
8373
|
}
|
|
7725
8374
|
async function loadHealthWarnings(vault) {
|
|
7726
|
-
const text = await readIfExists2(
|
|
8375
|
+
const text = await readIfExists2(join38(vault, ".skillwiki", "health.json"));
|
|
7727
8376
|
if (!text) return [];
|
|
7728
8377
|
try {
|
|
7729
8378
|
const parsed = JSON.parse(text);
|
|
@@ -7736,11 +8385,11 @@ async function loadHealthWarnings(vault) {
|
|
|
7736
8385
|
}
|
|
7737
8386
|
}
|
|
7738
8387
|
async function writeBriefArtifacts(vault, input) {
|
|
7739
|
-
const metaPath =
|
|
7740
|
-
const cacheMdPath =
|
|
7741
|
-
const cacheJsonPath =
|
|
7742
|
-
await mkdir13(
|
|
7743
|
-
await mkdir13(
|
|
8388
|
+
const metaPath = join38(vault, "meta", "latest-session-brief.md");
|
|
8389
|
+
const cacheMdPath = join38(vault, ".skillwiki", "session-brief.md");
|
|
8390
|
+
const cacheJsonPath = join38(vault, ".skillwiki", "session-brief.json");
|
|
8391
|
+
await mkdir13(join38(vault, "meta"), { recursive: true });
|
|
8392
|
+
await mkdir13(join38(vault, ".skillwiki"), { recursive: true });
|
|
7744
8393
|
const committed = renderCommittedBrief(input);
|
|
7745
8394
|
const previousComparable = comparableBrief(await readIfExists2(metaPath));
|
|
7746
8395
|
const nextComparable = comparableBrief(committed);
|
|
@@ -7800,7 +8449,7 @@ function renderCommittedBrief(input) {
|
|
|
7800
8449
|
].filter((line) => line !== "").join("\n");
|
|
7801
8450
|
}
|
|
7802
8451
|
async function ensureIndexEntry(vault) {
|
|
7803
|
-
const indexPath =
|
|
8452
|
+
const indexPath = join38(vault, "index.md");
|
|
7804
8453
|
let text = await readIfExists2(indexPath);
|
|
7805
8454
|
if (!text) return false;
|
|
7806
8455
|
if (text.includes("[[meta/latest-session-brief]]")) return false;
|
|
@@ -7819,7 +8468,7 @@ async function ensureIndexEntry(vault) {
|
|
|
7819
8468
|
return true;
|
|
7820
8469
|
}
|
|
7821
8470
|
async function appendMaterialLog(vault, today) {
|
|
7822
|
-
const logPath =
|
|
8471
|
+
const logPath = join38(vault, "log.md");
|
|
7823
8472
|
const text = await readIfExists2(logPath);
|
|
7824
8473
|
if (!text) return false;
|
|
7825
8474
|
const entry = `
|
|
@@ -7829,7 +8478,7 @@ async function appendMaterialLog(vault, today) {
|
|
|
7829
8478
|
}
|
|
7830
8479
|
async function readIfExists2(path) {
|
|
7831
8480
|
try {
|
|
7832
|
-
return await
|
|
8481
|
+
return await readFile25(path, "utf8");
|
|
7833
8482
|
} catch {
|
|
7834
8483
|
return "";
|
|
7835
8484
|
}
|
|
@@ -7871,9 +8520,9 @@ function dateFromPath(path) {
|
|
|
7871
8520
|
}
|
|
7872
8521
|
|
|
7873
8522
|
// src/commands/ingest.ts
|
|
7874
|
-
import { readFile as
|
|
7875
|
-
import { join as
|
|
7876
|
-
import { createHash as
|
|
8523
|
+
import { readFile as readFile26, writeFile as writeFile15, mkdir as mkdir14 } from "fs/promises";
|
|
8524
|
+
import { join as join39 } from "path";
|
|
8525
|
+
import { createHash as createHash7 } from "crypto";
|
|
7877
8526
|
var ALLOWED_TYPES = /* @__PURE__ */ new Set(["entity", "concept", "comparison", "query"]);
|
|
7878
8527
|
var TYPE_DIR = {
|
|
7879
8528
|
entity: "entities",
|
|
@@ -8031,7 +8680,7 @@ async function runIngest(input) {
|
|
|
8031
8680
|
sourceContent = fetchResult.data.body;
|
|
8032
8681
|
} else {
|
|
8033
8682
|
try {
|
|
8034
|
-
sourceContent = await
|
|
8683
|
+
sourceContent = await readFile26(input.source, "utf8");
|
|
8035
8684
|
} catch {
|
|
8036
8685
|
return {
|
|
8037
8686
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -8039,15 +8688,25 @@ async function runIngest(input) {
|
|
|
8039
8688
|
};
|
|
8040
8689
|
}
|
|
8041
8690
|
}
|
|
8042
|
-
const
|
|
8691
|
+
const sensitiveFindings = scanSensitiveContent(sourceContent, { file: input.source });
|
|
8692
|
+
if (sensitiveFindings.length > 0) {
|
|
8693
|
+
return {
|
|
8694
|
+
exitCode: ExitCode.SENSITIVE_CONTENT_DETECTED,
|
|
8695
|
+
result: err("SENSITIVE_CONTENT_DETECTED", {
|
|
8696
|
+
message: "source content contains sensitive authentication material; provide a redacted source before ingesting",
|
|
8697
|
+
findings: sensitiveFindings
|
|
8698
|
+
})
|
|
8699
|
+
};
|
|
8700
|
+
}
|
|
8701
|
+
const sha256 = createHash7("sha256").update(Buffer.from(sourceContent, "utf8")).digest("hex");
|
|
8043
8702
|
const today = todayIso();
|
|
8044
8703
|
const slug = slugify3(input.title);
|
|
8045
8704
|
const tags = input.tags && input.tags.length > 0 ? input.tags : [];
|
|
8046
8705
|
const rawRelPath = `raw/articles/${slug}.md`;
|
|
8047
8706
|
const typedDir = TYPE_DIR[input.type] ?? `${input.type}s`;
|
|
8048
8707
|
const typedRelPath = `${typedDir}/${slug}.md`;
|
|
8049
|
-
const rawAbsPath =
|
|
8050
|
-
const typedAbsPath =
|
|
8708
|
+
const rawAbsPath = join39(input.vault, rawRelPath);
|
|
8709
|
+
const typedAbsPath = join39(input.vault, typedRelPath);
|
|
8051
8710
|
const identity = assessSourceIdentity({
|
|
8052
8711
|
rawPath: rawRelPath,
|
|
8053
8712
|
sourceUrl: sourceUrl ?? void 0,
|
|
@@ -8129,7 +8788,7 @@ async function runIngest(input) {
|
|
|
8129
8788
|
};
|
|
8130
8789
|
}
|
|
8131
8790
|
try {
|
|
8132
|
-
await mkdir14(
|
|
8791
|
+
await mkdir14(join39(input.vault, "raw", "articles"), { recursive: true });
|
|
8133
8792
|
await writeFile15(rawAbsPath, rawContent, "utf8");
|
|
8134
8793
|
} catch (e) {
|
|
8135
8794
|
return {
|
|
@@ -8138,7 +8797,7 @@ async function runIngest(input) {
|
|
|
8138
8797
|
};
|
|
8139
8798
|
}
|
|
8140
8799
|
try {
|
|
8141
|
-
await mkdir14(
|
|
8800
|
+
await mkdir14(join39(input.vault, typedDir), { recursive: true });
|
|
8142
8801
|
await writeFile15(typedAbsPath, typedContent, "utf8");
|
|
8143
8802
|
} catch (e) {
|
|
8144
8803
|
return {
|
|
@@ -8318,19 +8977,19 @@ ${body}`;
|
|
|
8318
8977
|
|
|
8319
8978
|
// src/commands/sync.ts
|
|
8320
8979
|
import { existsSync as existsSync17 } from "fs";
|
|
8321
|
-
import { join as
|
|
8980
|
+
import { join as join41 } from "path";
|
|
8322
8981
|
|
|
8323
8982
|
// src/utils/sync-lock.ts
|
|
8324
8983
|
import { existsSync as existsSync16, mkdirSync as mkdirSync5, readFileSync as readFileSync11, renameSync as renameSync2, unlinkSync as unlinkSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
8325
|
-
import { join as
|
|
8326
|
-
import { createHash as
|
|
8984
|
+
import { join as join40 } from "path";
|
|
8985
|
+
import { createHash as createHash8 } from "crypto";
|
|
8327
8986
|
function getSessionId() {
|
|
8328
8987
|
if (process.env.CLAUDE_SESSION_ID) return process.env.CLAUDE_SESSION_ID;
|
|
8329
8988
|
if (process.env.SKILLWIKI_SESSION_ID) return process.env.SKILLWIKI_SESSION_ID;
|
|
8330
8989
|
return process.pid.toString();
|
|
8331
8990
|
}
|
|
8332
8991
|
function lockPath(vault) {
|
|
8333
|
-
return
|
|
8992
|
+
return join40(vault, ".skillwiki", "sync.lock");
|
|
8334
8993
|
}
|
|
8335
8994
|
function readLock(vault) {
|
|
8336
8995
|
const path = lockPath(vault);
|
|
@@ -8349,7 +9008,7 @@ function isStale(lock, now) {
|
|
|
8349
9008
|
}
|
|
8350
9009
|
function acquireLock(vault, opts = {}) {
|
|
8351
9010
|
const path = lockPath(vault);
|
|
8352
|
-
const dir =
|
|
9011
|
+
const dir = join40(vault, ".skillwiki");
|
|
8353
9012
|
if (!existsSync16(dir)) {
|
|
8354
9013
|
mkdirSync5(dir, { recursive: true });
|
|
8355
9014
|
}
|
|
@@ -8424,7 +9083,7 @@ function releaseLock(vault, opts = {}) {
|
|
|
8424
9083
|
function runSyncStatus(input) {
|
|
8425
9084
|
const vault = input.vault;
|
|
8426
9085
|
const includeStashes = input.includeStashes ?? false;
|
|
8427
|
-
if (!existsSync17(
|
|
9086
|
+
if (!existsSync17(join41(vault, ".git"))) {
|
|
8428
9087
|
return {
|
|
8429
9088
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
8430
9089
|
result: ok({
|
|
@@ -8502,7 +9161,7 @@ function runSyncStatus(input) {
|
|
|
8502
9161
|
}
|
|
8503
9162
|
async function runSyncPush(input) {
|
|
8504
9163
|
const vault = input.vault;
|
|
8505
|
-
if (!existsSync17(
|
|
9164
|
+
if (!existsSync17(join41(vault, ".git"))) {
|
|
8506
9165
|
return {
|
|
8507
9166
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
8508
9167
|
result: err("NOT_A_GIT_REPO", { path: vault })
|
|
@@ -8625,7 +9284,7 @@ function enableGitLongPathsOnWindows(vault) {
|
|
|
8625
9284
|
}
|
|
8626
9285
|
async function runSyncPull(input) {
|
|
8627
9286
|
const vault = input.vault;
|
|
8628
|
-
if (!existsSync17(
|
|
9287
|
+
if (!existsSync17(join41(vault, ".git"))) {
|
|
8629
9288
|
return {
|
|
8630
9289
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
8631
9290
|
result: err("NOT_A_GIT_REPO", { path: vault })
|
|
@@ -8866,7 +9525,7 @@ function runSyncUnlock(input) {
|
|
|
8866
9525
|
|
|
8867
9526
|
// src/commands/backup.ts
|
|
8868
9527
|
import { statSync as statSync5, readdirSync as readdirSync3, readFileSync as readFileSync12, mkdirSync as mkdirSync6, writeFileSync as writeFileSync8 } from "fs";
|
|
8869
|
-
import { join as
|
|
9528
|
+
import { join as join42, relative as relative4, dirname as dirname13 } from "path";
|
|
8870
9529
|
import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
|
|
8871
9530
|
|
|
8872
9531
|
// src/utils/s3-client.ts
|
|
@@ -8890,7 +9549,7 @@ var SKIP_DIRS2 = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node
|
|
|
8890
9549
|
function* walkMarkdown(dir, base) {
|
|
8891
9550
|
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
8892
9551
|
if (SKIP_DIRS2.has(entry.name)) continue;
|
|
8893
|
-
const full =
|
|
9552
|
+
const full = join42(dir, entry.name);
|
|
8894
9553
|
if (entry.isDirectory()) {
|
|
8895
9554
|
yield* walkMarkdown(full, base);
|
|
8896
9555
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -8913,7 +9572,7 @@ async function runBackupSync(input) {
|
|
|
8913
9572
|
let failed = 0;
|
|
8914
9573
|
const files = [...walkMarkdown(input.vault, input.vault)];
|
|
8915
9574
|
for (const relPath of files) {
|
|
8916
|
-
const absPath =
|
|
9575
|
+
const absPath = join42(input.vault, relPath);
|
|
8917
9576
|
const localStat = statSync5(absPath);
|
|
8918
9577
|
let needsUpload = true;
|
|
8919
9578
|
try {
|
|
@@ -8989,7 +9648,7 @@ async function runBackupRestore(input) {
|
|
|
8989
9648
|
const objects = list.Contents ?? [];
|
|
8990
9649
|
for (const obj of objects) {
|
|
8991
9650
|
if (!obj.Key) continue;
|
|
8992
|
-
const localPath =
|
|
9651
|
+
const localPath = join42(target, obj.Key);
|
|
8993
9652
|
try {
|
|
8994
9653
|
const localStat = statSync5(localPath);
|
|
8995
9654
|
if (obj.LastModified && localStat.mtime > obj.LastModified) {
|
|
@@ -9036,8 +9695,8 @@ async function runBackupRestore(input) {
|
|
|
9036
9695
|
|
|
9037
9696
|
// src/commands/status.ts
|
|
9038
9697
|
import { existsSync as existsSync18, statSync as statSync6 } from "fs";
|
|
9039
|
-
import { readFile as
|
|
9040
|
-
import { join as
|
|
9698
|
+
import { readFile as readFile27 } from "fs/promises";
|
|
9699
|
+
import { join as join43 } from "path";
|
|
9041
9700
|
async function runStatus(input) {
|
|
9042
9701
|
if (!existsSync18(input.vault)) {
|
|
9043
9702
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
|
|
@@ -9064,7 +9723,7 @@ async function runStatus(input) {
|
|
|
9064
9723
|
const compound = scan.data.compound.length;
|
|
9065
9724
|
let schemaVersion = "v1";
|
|
9066
9725
|
try {
|
|
9067
|
-
const schemaContent = await
|
|
9726
|
+
const schemaContent = await readFile27(join43(input.vault, "SCHEMA.md"), "utf8");
|
|
9068
9727
|
const versionMatch = schemaContent.match(/version:\s*["']?([^"'\s\n]+)/i);
|
|
9069
9728
|
if (versionMatch) schemaVersion = versionMatch[1];
|
|
9070
9729
|
} catch {
|
|
@@ -9125,7 +9784,7 @@ async function runStatus(input) {
|
|
|
9125
9784
|
|
|
9126
9785
|
// src/commands/seed.ts
|
|
9127
9786
|
import { mkdir as mkdir15, writeFile as writeFile16, stat as stat9 } from "fs/promises";
|
|
9128
|
-
import { join as
|
|
9787
|
+
import { join as join44 } from "path";
|
|
9129
9788
|
var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
9130
9789
|
var EXAMPLE_PAGES = {
|
|
9131
9790
|
"entities/example-project.md": `---
|
|
@@ -9194,29 +9853,29 @@ Real sources are immutable after ingestion \u2014 never edit them.
|
|
|
9194
9853
|
`;
|
|
9195
9854
|
async function runSeed(input) {
|
|
9196
9855
|
try {
|
|
9197
|
-
await stat9(
|
|
9856
|
+
await stat9(join44(input.vault, "SCHEMA.md"));
|
|
9198
9857
|
} catch {
|
|
9199
9858
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { root: input.vault, reason: "SCHEMA.md missing \u2014 run `skillwiki init` first" }) };
|
|
9200
9859
|
}
|
|
9201
9860
|
const created = [];
|
|
9202
9861
|
const skipped = [];
|
|
9203
9862
|
for (const [relPath, content] of Object.entries(EXAMPLE_PAGES)) {
|
|
9204
|
-
const absPath =
|
|
9863
|
+
const absPath = join44(input.vault, relPath);
|
|
9205
9864
|
try {
|
|
9206
9865
|
await stat9(absPath);
|
|
9207
9866
|
skipped.push(relPath);
|
|
9208
9867
|
} catch {
|
|
9209
|
-
await mkdir15(
|
|
9868
|
+
await mkdir15(join44(absPath, ".."), { recursive: true });
|
|
9210
9869
|
await writeFile16(absPath, content, "utf8");
|
|
9211
9870
|
created.push(relPath);
|
|
9212
9871
|
}
|
|
9213
9872
|
}
|
|
9214
|
-
const rawPath =
|
|
9873
|
+
const rawPath = join44(input.vault, "raw", "articles", "example-source.md");
|
|
9215
9874
|
try {
|
|
9216
9875
|
await stat9(rawPath);
|
|
9217
9876
|
skipped.push("raw/articles/example-source.md");
|
|
9218
9877
|
} catch {
|
|
9219
|
-
await mkdir15(
|
|
9878
|
+
await mkdir15(join44(rawPath, ".."), { recursive: true });
|
|
9220
9879
|
await writeFile16(rawPath, EXAMPLE_RAW, "utf8");
|
|
9221
9880
|
created.push("raw/articles/example-source.md");
|
|
9222
9881
|
}
|
|
@@ -9239,9 +9898,9 @@ async function runSeed(input) {
|
|
|
9239
9898
|
}
|
|
9240
9899
|
|
|
9241
9900
|
// src/commands/canvas.ts
|
|
9242
|
-
import { readFile as
|
|
9901
|
+
import { readFile as readFile28, writeFile as writeFile17 } from "fs/promises";
|
|
9243
9902
|
import { existsSync as existsSync19 } from "fs";
|
|
9244
|
-
import { join as
|
|
9903
|
+
import { join as join45 } from "path";
|
|
9245
9904
|
var NODE_WIDTH = 240;
|
|
9246
9905
|
var NODE_HEIGHT = 60;
|
|
9247
9906
|
var COLUMN_SPACING = 400;
|
|
@@ -9319,7 +9978,7 @@ function buildCanvasEdges(adjacency) {
|
|
|
9319
9978
|
return edges;
|
|
9320
9979
|
}
|
|
9321
9980
|
async function runCanvasGenerate(input) {
|
|
9322
|
-
const graphPath = input.graphPath ??
|
|
9981
|
+
const graphPath = input.graphPath ?? join45(input.vault, ".skillwiki", "graph.json");
|
|
9323
9982
|
if (!existsSync19(graphPath)) {
|
|
9324
9983
|
return {
|
|
9325
9984
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -9331,7 +9990,7 @@ async function runCanvasGenerate(input) {
|
|
|
9331
9990
|
}
|
|
9332
9991
|
let raw;
|
|
9333
9992
|
try {
|
|
9334
|
-
raw = await
|
|
9993
|
+
raw = await readFile28(graphPath, "utf8");
|
|
9335
9994
|
} catch (e) {
|
|
9336
9995
|
return {
|
|
9337
9996
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -9357,7 +10016,7 @@ async function runCanvasGenerate(input) {
|
|
|
9357
10016
|
const nodes = buildCanvasNodes(paths);
|
|
9358
10017
|
const edges = buildCanvasEdges(graph.adjacency);
|
|
9359
10018
|
const canvas = { nodes, edges };
|
|
9360
|
-
const outPath =
|
|
10019
|
+
const outPath = join45(input.vault, "vault-graph.canvas");
|
|
9361
10020
|
try {
|
|
9362
10021
|
await writeFile17(outPath, JSON.stringify(canvas, null, 2));
|
|
9363
10022
|
} catch (e) {
|
|
@@ -9379,8 +10038,8 @@ written: ${outPath}`
|
|
|
9379
10038
|
}
|
|
9380
10039
|
|
|
9381
10040
|
// src/commands/query.ts
|
|
9382
|
-
import { readFile as
|
|
9383
|
-
import { join as
|
|
10041
|
+
import { readFile as readFile29, stat as stat10 } from "fs/promises";
|
|
10042
|
+
import { join as join46 } from "path";
|
|
9384
10043
|
var W_KEYWORD = 2;
|
|
9385
10044
|
var W_SOURCE_OVERLAP = 4;
|
|
9386
10045
|
var W_WIKILINK = 3;
|
|
@@ -9501,7 +10160,7 @@ function computeKeywordScore(terms, title, tags, body) {
|
|
|
9501
10160
|
return score;
|
|
9502
10161
|
}
|
|
9503
10162
|
async function loadOrBuildGraph(vault) {
|
|
9504
|
-
const graphPath =
|
|
10163
|
+
const graphPath = join46(vault, ".skillwiki", "graph.json");
|
|
9505
10164
|
let needsBuild = false;
|
|
9506
10165
|
try {
|
|
9507
10166
|
const fileStat = await stat10(graphPath);
|
|
@@ -9515,265 +10174,13 @@ async function loadOrBuildGraph(vault) {
|
|
|
9515
10174
|
if (buildResult.exitCode !== 0) return null;
|
|
9516
10175
|
}
|
|
9517
10176
|
try {
|
|
9518
|
-
const raw = await
|
|
10177
|
+
const raw = await readFile29(graphPath, "utf8");
|
|
9519
10178
|
return JSON.parse(raw);
|
|
9520
10179
|
} catch {
|
|
9521
10180
|
return null;
|
|
9522
10181
|
}
|
|
9523
10182
|
}
|
|
9524
10183
|
|
|
9525
|
-
// src/commands/fleet.ts
|
|
9526
|
-
import { readFile as readFile29 } from "fs/promises";
|
|
9527
|
-
import { hostname as nodeHostname, userInfo } from "os";
|
|
9528
|
-
import { join as join46 } from "path";
|
|
9529
|
-
import yaml3 from "js-yaml";
|
|
9530
|
-
var FLEET_REL_PATH = join46("projects", "llm-wiki", "architecture", "fleet.yaml");
|
|
9531
|
-
async function runFleetValidate(input) {
|
|
9532
|
-
const loaded = await loadFleetManifest(input.file);
|
|
9533
|
-
if (!loaded.ok) {
|
|
9534
|
-
if (loaded.error === "FILE_NOT_FOUND") {
|
|
9535
|
-
return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: input.file }) };
|
|
9536
|
-
}
|
|
9537
|
-
const errors = fleetLoadErrors(loaded);
|
|
9538
|
-
return invalidFleet(errors);
|
|
9539
|
-
}
|
|
9540
|
-
const warnings = fleetWarnings(loaded.manifest);
|
|
9541
|
-
const snapshotter = findSnapshotter(loaded.manifest);
|
|
9542
|
-
return {
|
|
9543
|
-
exitCode: ExitCode.OK,
|
|
9544
|
-
result: ok({
|
|
9545
|
-
valid: true,
|
|
9546
|
-
errors: [],
|
|
9547
|
-
warnings,
|
|
9548
|
-
host_count: Object.keys(loaded.manifest.hosts).length,
|
|
9549
|
-
snapshotter,
|
|
9550
|
-
humanHint: `VALID fleet manifest (${Object.keys(loaded.manifest.hosts).length} hosts; snapshotter: ${snapshotter ?? "none"})`
|
|
9551
|
-
})
|
|
9552
|
-
};
|
|
9553
|
-
}
|
|
9554
|
-
async function runFleetContext(input) {
|
|
9555
|
-
const env = input.env ?? process.env;
|
|
9556
|
-
const home = input.home ?? env.HOME ?? "";
|
|
9557
|
-
const cwd = input.cwd ?? process.cwd();
|
|
9558
|
-
const osHostname = input.osHostname ?? safeEnvValue(env.HOSTNAME) ?? nodeHostname();
|
|
9559
|
-
const user = input.user ?? safeEnvValue(env.USER) ?? safeUserName();
|
|
9560
|
-
const vault = input.vault ?? safeEnvValue(env.WIKI_PATH);
|
|
9561
|
-
const file = input.file ?? (vault ? join46(vault, FLEET_REL_PATH) : void 0);
|
|
9562
|
-
const loaded = file ? await loadFleetManifest(file) : { ok: false, error: "FILE_NOT_FOUND" };
|
|
9563
|
-
if (!loaded.ok) {
|
|
9564
|
-
const markdown2 = formatUnknownContext({ osHostname, user, cwd, vault, reason: "fleet manifest unavailable or invalid" });
|
|
9565
|
-
return {
|
|
9566
|
-
exitCode: ExitCode.OK,
|
|
9567
|
-
result: ok({ manifest_loaded: false, markdown: markdown2, humanHint: markdown2 })
|
|
9568
|
-
};
|
|
9569
|
-
}
|
|
9570
|
-
const resolved = await resolveHostId({
|
|
9571
|
-
manifest: loaded.manifest,
|
|
9572
|
-
hostId: input.hostId,
|
|
9573
|
-
env,
|
|
9574
|
-
home,
|
|
9575
|
-
osHostname
|
|
9576
|
-
});
|
|
9577
|
-
if (!resolved.hostId || !loaded.manifest.hosts[resolved.hostId]) {
|
|
9578
|
-
const markdown2 = formatUnknownContext({ osHostname, user, cwd, vault, reason: "host identity is unresolved" });
|
|
9579
|
-
return {
|
|
9580
|
-
exitCode: ExitCode.OK,
|
|
9581
|
-
result: ok({ manifest_loaded: true, markdown: markdown2, humanHint: markdown2 })
|
|
9582
|
-
};
|
|
9583
|
-
}
|
|
9584
|
-
const markdown = formatKnownContext({
|
|
9585
|
-
manifest: loaded.manifest,
|
|
9586
|
-
hostId: resolved.hostId,
|
|
9587
|
-
source: resolved.source,
|
|
9588
|
-
osHostname,
|
|
9589
|
-
user,
|
|
9590
|
-
cwd,
|
|
9591
|
-
vault
|
|
9592
|
-
});
|
|
9593
|
-
return {
|
|
9594
|
-
exitCode: ExitCode.OK,
|
|
9595
|
-
result: ok({
|
|
9596
|
-
manifest_loaded: true,
|
|
9597
|
-
host_id: resolved.hostId,
|
|
9598
|
-
source: resolved.source,
|
|
9599
|
-
markdown,
|
|
9600
|
-
humanHint: markdown
|
|
9601
|
-
})
|
|
9602
|
-
};
|
|
9603
|
-
}
|
|
9604
|
-
async function loadFleetManifest(file) {
|
|
9605
|
-
let text;
|
|
9606
|
-
try {
|
|
9607
|
-
text = await readFile29(file, "utf8");
|
|
9608
|
-
} catch {
|
|
9609
|
-
return { ok: false, error: "FILE_NOT_FOUND" };
|
|
9610
|
-
}
|
|
9611
|
-
let parsed;
|
|
9612
|
-
try {
|
|
9613
|
-
parsed = yaml3.load(text, { schema: yaml3.JSON_SCHEMA });
|
|
9614
|
-
} catch (error) {
|
|
9615
|
-
return { ok: false, error: "INVALID_YAML", detail: error instanceof Error ? error.message : String(error) };
|
|
9616
|
-
}
|
|
9617
|
-
const result = FleetManifestSchema.safeParse(parsed);
|
|
9618
|
-
if (!result.success) {
|
|
9619
|
-
return { ok: false, error: "INVALID_FLEET_MANIFEST", detail: result.error.issues };
|
|
9620
|
-
}
|
|
9621
|
-
return { ok: true, manifest: result.data };
|
|
9622
|
-
}
|
|
9623
|
-
function invalidFleet(errors) {
|
|
9624
|
-
return {
|
|
9625
|
-
exitCode: ExitCode.FLEET_MANIFEST_INVALID,
|
|
9626
|
-
result: ok({
|
|
9627
|
-
valid: false,
|
|
9628
|
-
errors,
|
|
9629
|
-
warnings: [],
|
|
9630
|
-
host_count: 0,
|
|
9631
|
-
humanHint: `INVALID fleet manifest
|
|
9632
|
-
${errors.map((e) => ` ${e.path || "(root)"}: ${e.message}`).join("\n")}`
|
|
9633
|
-
})
|
|
9634
|
-
};
|
|
9635
|
-
}
|
|
9636
|
-
function fleetLoadErrors(loaded) {
|
|
9637
|
-
if (loaded.error === "INVALID_YAML") {
|
|
9638
|
-
return [{ path: "", message: `invalid YAML: ${String(loaded.detail ?? "parse failed")}` }];
|
|
9639
|
-
}
|
|
9640
|
-
if (loaded.error === "INVALID_FLEET_MANIFEST" && Array.isArray(loaded.detail)) {
|
|
9641
|
-
return loaded.detail.map((issue) => {
|
|
9642
|
-
const zodIssue = issue;
|
|
9643
|
-
return {
|
|
9644
|
-
path: (zodIssue.path ?? []).join("."),
|
|
9645
|
-
message: zodIssue.message ?? "invalid value"
|
|
9646
|
-
};
|
|
9647
|
-
});
|
|
9648
|
-
}
|
|
9649
|
-
return [{ path: "", message: loaded.error }];
|
|
9650
|
-
}
|
|
9651
|
-
function fleetWarnings(manifest) {
|
|
9652
|
-
const warnings = [];
|
|
9653
|
-
for (const [id, host] of Object.entries(manifest.hosts)) {
|
|
9654
|
-
if (host.role === "snapshotter" && host.protected !== true) {
|
|
9655
|
-
warnings.push(`snapshotter host '${id}' is not protected=true`);
|
|
9656
|
-
}
|
|
9657
|
-
}
|
|
9658
|
-
return warnings;
|
|
9659
|
-
}
|
|
9660
|
-
function findSnapshotter(manifest) {
|
|
9661
|
-
return Object.entries(manifest.hosts).find(([, host]) => host.role === "snapshotter")?.[0];
|
|
9662
|
-
}
|
|
9663
|
-
async function resolveHostId(input) {
|
|
9664
|
-
if (input.hostId) return { hostId: input.hostId, source: "host-id" };
|
|
9665
|
-
if (input.env.SKILLWIKI_HOST_ID) return { hostId: input.env.SKILLWIKI_HOST_ID, source: "SKILLWIKI_HOST_ID" };
|
|
9666
|
-
if (input.env.AGENT_HOST_ID) return { hostId: input.env.AGENT_HOST_ID, source: "AGENT_HOST_ID" };
|
|
9667
|
-
if (input.home) {
|
|
9668
|
-
const dotenv = await parseDotenvFile(join46(input.home, ".skillwiki", ".env"));
|
|
9669
|
-
if (dotenv.SKILLWIKI_HOST_ID) {
|
|
9670
|
-
return { hostId: dotenv.SKILLWIKI_HOST_ID, source: "~/.skillwiki/.env:SKILLWIKI_HOST_ID" };
|
|
9671
|
-
}
|
|
9672
|
-
}
|
|
9673
|
-
if (input.env.VS_HOSTNAME) return { hostId: input.env.VS_HOSTNAME, source: "VS_HOSTNAME" };
|
|
9674
|
-
const hostname = input.osHostname.trim();
|
|
9675
|
-
if (hostname) {
|
|
9676
|
-
if (input.manifest.hosts[hostname]) return { hostId: hostname, source: "hostname" };
|
|
9677
|
-
const byHostname = Object.entries(input.manifest.hosts).find(([, host]) => host.identity.hostnames.includes(hostname));
|
|
9678
|
-
if (byHostname) return { hostId: byHostname[0], source: "hostname" };
|
|
9679
|
-
}
|
|
9680
|
-
return {};
|
|
9681
|
-
}
|
|
9682
|
-
function formatKnownContext(input) {
|
|
9683
|
-
const host = input.manifest.hosts[input.hostId];
|
|
9684
|
-
const protectedValue = host.protected === true ? "true" : "false";
|
|
9685
|
-
const writesTo = host.writes_to.join(", ");
|
|
9686
|
-
const selfAliases = collectSelfAliases(input.manifest, input.hostId);
|
|
9687
|
-
const outbound = collectOutboundAccess(input.manifest, input.hostId);
|
|
9688
|
-
const maintenanceLines = formatMaintenanceLines(host);
|
|
9689
|
-
const guidance = input.hostId === "macos-dev" ? "use declared SSH aliases for remote work when needed; do not assume undeclared hosts have reciprocal SSH access." : `this session is already on \`${input.hostId}\`; do not SSH to self aliases unless the user explicitly asks. Do not assume outbound SSH to other fleet hosts is configured.`;
|
|
9690
|
-
return [
|
|
9691
|
-
"## Runtime Host Context",
|
|
9692
|
-
"",
|
|
9693
|
-
`- Current machine: \`${input.hostId}\`${input.source ? ` (source: \`${input.source}\`)` : ""}`,
|
|
9694
|
-
`- OS hostname: ${formatMaybe(input.osHostname)}`,
|
|
9695
|
-
`- User: ${formatMaybe(input.user)}`,
|
|
9696
|
-
`- Workspace: ${formatMaybe(input.cwd)}`,
|
|
9697
|
-
`- Vault: ${formatMaybe(input.vault)}`,
|
|
9698
|
-
`- Fleet role: \`${host.role}\`; protected: \`${protectedValue}\`; writes_to: \`${writesTo}\``,
|
|
9699
|
-
...maintenanceLines,
|
|
9700
|
-
`- Self SSH aliases known in fleet: ${formatList(selfAliases)}`,
|
|
9701
|
-
`- Declared outbound SSH from this source: ${formatOutboundAccess(outbound)}`,
|
|
9702
|
-
`- Guidance: ${guidance}`
|
|
9703
|
-
].join("\n");
|
|
9704
|
-
}
|
|
9705
|
-
function formatUnknownContext(input) {
|
|
9706
|
-
return [
|
|
9707
|
-
"## Runtime Host Context",
|
|
9708
|
-
"",
|
|
9709
|
-
"- Current machine: unknown",
|
|
9710
|
-
`- OS hostname: ${formatMaybe(input.osHostname)}`,
|
|
9711
|
-
`- User: ${formatMaybe(input.user)}`,
|
|
9712
|
-
`- Workspace: ${formatMaybe(input.cwd)}`,
|
|
9713
|
-
`- Vault: ${formatMaybe(input.vault)}`,
|
|
9714
|
-
"- Fleet role: unknown",
|
|
9715
|
-
"- Self SSH aliases known in fleet: unknown",
|
|
9716
|
-
"- Declared outbound SSH from this source: unknown",
|
|
9717
|
-
`- Guidance: ${input.reason}; do not assume local vs remote role. Inspect runtime or ask before SSH/deploy/sync work.`
|
|
9718
|
-
].join("\n");
|
|
9719
|
-
}
|
|
9720
|
-
function collectSelfAliases(manifest, hostId2) {
|
|
9721
|
-
const aliases = [];
|
|
9722
|
-
const host = manifest.hosts[hostId2];
|
|
9723
|
-
const access = host?.access?.from ?? {};
|
|
9724
|
-
for (const profile of Object.values(access)) {
|
|
9725
|
-
for (const alias of profile.ssh_aliases ?? []) aliases.push(alias);
|
|
9726
|
-
}
|
|
9727
|
-
return [...new Set(aliases)];
|
|
9728
|
-
}
|
|
9729
|
-
function collectOutboundAccess(manifest, sourceHostId) {
|
|
9730
|
-
const hosts = [];
|
|
9731
|
-
for (const [targetId, target] of Object.entries(manifest.hosts)) {
|
|
9732
|
-
if (targetId === sourceHostId) continue;
|
|
9733
|
-
const profile = target.access?.from?.[sourceHostId];
|
|
9734
|
-
if (profile && (profile.status === "configured" || profile.status === "local")) {
|
|
9735
|
-
hosts.push({
|
|
9736
|
-
hostId: targetId,
|
|
9737
|
-
sshAliases: [...new Set(profile.ssh_aliases ?? [])],
|
|
9738
|
-
users: [...new Set(profile.users ?? [])]
|
|
9739
|
-
});
|
|
9740
|
-
}
|
|
9741
|
-
}
|
|
9742
|
-
return hosts.sort((left, right) => left.hostId.localeCompare(right.hostId));
|
|
9743
|
-
}
|
|
9744
|
-
function formatMaintenanceLines(host) {
|
|
9745
|
-
const satellite = host.maintenance?.skillwiki_satellite;
|
|
9746
|
-
if (!satellite?.enabled) return [];
|
|
9747
|
-
return [
|
|
9748
|
-
`- Maintenance role: \`skillwiki satellite\`; user: \`${satellite.user}\`; ssh: \`${satellite.ssh_alias}\``,
|
|
9749
|
-
`- Maintenance paths: maintenance vault: \`${satellite.vault_path}\`; repo: \`${satellite.repo_path}\`; scheduler: \`${satellite.scheduler}\`; jobs: ${formatList(satellite.jobs)}`
|
|
9750
|
-
];
|
|
9751
|
-
}
|
|
9752
|
-
function formatOutboundAccess(values) {
|
|
9753
|
-
if (values.length === 0) return "none";
|
|
9754
|
-
return values.map((value) => {
|
|
9755
|
-
const aliasPart = value.sshAliases.length > 0 ? ` via ${formatList(value.sshAliases)}` : " (no SSH aliases)";
|
|
9756
|
-
const usersPart = value.users.length > 0 ? ` (users: ${formatList(value.users)})` : "";
|
|
9757
|
-
return `\`${value.hostId}\`${aliasPart}${usersPart}`;
|
|
9758
|
-
}).join("; ");
|
|
9759
|
-
}
|
|
9760
|
-
function formatList(values) {
|
|
9761
|
-
return values.length > 0 ? values.map((v) => `\`${v}\``).join(", ") : "none";
|
|
9762
|
-
}
|
|
9763
|
-
function formatMaybe(value) {
|
|
9764
|
-
return value && value.trim().length > 0 ? `\`${value}\`` : "unknown";
|
|
9765
|
-
}
|
|
9766
|
-
function safeEnvValue(value) {
|
|
9767
|
-
return value && value.trim().length > 0 ? value : void 0;
|
|
9768
|
-
}
|
|
9769
|
-
function safeUserName() {
|
|
9770
|
-
try {
|
|
9771
|
-
return userInfo().username;
|
|
9772
|
-
} catch {
|
|
9773
|
-
return "";
|
|
9774
|
-
}
|
|
9775
|
-
}
|
|
9776
|
-
|
|
9777
10184
|
// src/utils/auto-commit.ts
|
|
9778
10185
|
import { existsSync as existsSync20 } from "fs";
|
|
9779
10186
|
import { join as join47 } from "path";
|