threadnote 0.6.2 → 0.6.3
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/threadnote.cjs +82 -11
- package/package.json +1 -1
package/dist/threadnote.cjs
CHANGED
|
@@ -3488,6 +3488,7 @@ var SHIM_MARKER = "Generated by threadnote";
|
|
|
3488
3488
|
var USER_INSTRUCTIONS_START_MARKER = "<!-- BEGIN THREADNOTE USER INSTRUCTIONS -->";
|
|
3489
3489
|
var USER_INSTRUCTIONS_END_MARKER = "<!-- END THREADNOTE USER INSTRUCTIONS -->";
|
|
3490
3490
|
var USER_MANIFEST_NAME = "seed-manifest.yaml";
|
|
3491
|
+
var SEED_STATE_FILE = "seed-state.json";
|
|
3491
3492
|
var USER_AGENT_INSTRUCTION_TARGETS = [
|
|
3492
3493
|
{ kind: "block", label: "codex user instructions", path: "~/.codex/AGENTS.md" },
|
|
3493
3494
|
{ kind: "block", label: "claude user instructions", path: "~/.claude/CLAUDE.md" },
|
|
@@ -4256,8 +4257,11 @@ function buildCopilotMcpServerConfig(config, options) {
|
|
|
4256
4257
|
type: "stdio"
|
|
4257
4258
|
};
|
|
4258
4259
|
}
|
|
4260
|
+
function isEmptyConfigContent(content) {
|
|
4261
|
+
return content === void 0 || content.trim().length === 0;
|
|
4262
|
+
}
|
|
4259
4263
|
function renderCursorMcpConfig(currentContent, name, serverConfig) {
|
|
4260
|
-
const parsed = currentContent
|
|
4264
|
+
const parsed = isEmptyConfigContent(currentContent) ? {} : parseJsonConfigObject(currentContent ?? "");
|
|
4261
4265
|
if (parsed === void 0) {
|
|
4262
4266
|
throw new Error(`${cursorMcpConfigPath()} exists but is not a JSON object; not modifying it.`);
|
|
4263
4267
|
}
|
|
@@ -4272,7 +4276,7 @@ function renderCursorMcpConfig(currentContent, name, serverConfig) {
|
|
|
4272
4276
|
`;
|
|
4273
4277
|
}
|
|
4274
4278
|
function renderCopilotMcpConfig(currentContent, name, serverConfig) {
|
|
4275
|
-
const parsed = currentContent
|
|
4279
|
+
const parsed = isEmptyConfigContent(currentContent) ? {} : parseJsonConfigObject(currentContent ?? "");
|
|
4276
4280
|
if (parsed === void 0) {
|
|
4277
4281
|
throw new Error(`${copilotMcpConfigPath()} exists but is not a JSON object; not modifying it.`);
|
|
4278
4282
|
}
|
|
@@ -4289,11 +4293,11 @@ function renderCopilotMcpConfig(currentContent, name, serverConfig) {
|
|
|
4289
4293
|
async function removeCursorMcpConfig(name, dryRun) {
|
|
4290
4294
|
const path = cursorMcpConfigPath();
|
|
4291
4295
|
const currentContent = await readFileIfExists(path);
|
|
4292
|
-
if (currentContent
|
|
4296
|
+
if (isEmptyConfigContent(currentContent)) {
|
|
4293
4297
|
console.log(`Already absent: ${path}`);
|
|
4294
4298
|
return;
|
|
4295
4299
|
}
|
|
4296
|
-
const parsed = parseJsonConfigObject(currentContent);
|
|
4300
|
+
const parsed = parseJsonConfigObject(currentContent ?? "");
|
|
4297
4301
|
if (parsed === void 0) {
|
|
4298
4302
|
console.log(`WARN ${path} exists but is not a JSON object; not modifying it.`);
|
|
4299
4303
|
return;
|
|
@@ -4318,11 +4322,11 @@ async function removeCursorMcpConfig(name, dryRun) {
|
|
|
4318
4322
|
async function removeCopilotMcpConfig(name, dryRun) {
|
|
4319
4323
|
const path = copilotMcpConfigPath();
|
|
4320
4324
|
const currentContent = await readFileIfExists(path);
|
|
4321
|
-
if (currentContent
|
|
4325
|
+
if (isEmptyConfigContent(currentContent)) {
|
|
4322
4326
|
console.log(`Already absent: ${path}`);
|
|
4323
4327
|
return;
|
|
4324
4328
|
}
|
|
4325
|
-
const parsed = parseJsonConfigObject(currentContent);
|
|
4329
|
+
const parsed = parseJsonConfigObject(currentContent ?? "");
|
|
4326
4330
|
if (parsed === void 0) {
|
|
4327
4331
|
console.log(`WARN ${path} exists but is not a JSON object; not modifying it.`);
|
|
4328
4332
|
return;
|
|
@@ -7698,8 +7702,8 @@ async function removeVikingResourceWithRetry(ov, config, uri) {
|
|
|
7698
7702
|
return false;
|
|
7699
7703
|
}
|
|
7700
7704
|
async function vikingResourceExists(ov, config, uri) {
|
|
7701
|
-
const
|
|
7702
|
-
return
|
|
7705
|
+
const stat4 = await runCommand(ov, withIdentity(config, ["stat", uri]), { allowFailure: true });
|
|
7706
|
+
return stat4.exitCode === 0;
|
|
7703
7707
|
}
|
|
7704
7708
|
async function ensureDurableMemoryDirectory(ov, config) {
|
|
7705
7709
|
await ensureMemoryDirectory(ov, config, durableMemoryDirectoryUri(config));
|
|
@@ -8481,9 +8485,13 @@ async function runSeed(config, options) {
|
|
|
8481
8485
|
const manifest = await readSeedManifest(config.manifestPath);
|
|
8482
8486
|
const ignorePatterns = await loadIgnorePatterns();
|
|
8483
8487
|
const ov = await openVikingCliForMode(options.dryRun === true);
|
|
8488
|
+
const statePath = (0, import_node_path8.join)(config.agentContextHome, SEED_STATE_FILE);
|
|
8489
|
+
const state = options.force === true ? { files: {}, version: 1 } : await readSeedState(statePath);
|
|
8490
|
+
const projects = filterProjects(manifest.projects, options.only);
|
|
8484
8491
|
let importedCount = 0;
|
|
8485
8492
|
let skippedCount = 0;
|
|
8486
|
-
|
|
8493
|
+
let unchangedCount = 0;
|
|
8494
|
+
for (const project of projects) {
|
|
8487
8495
|
const projectRoot = expandPath(project.path);
|
|
8488
8496
|
if (!await exists(projectRoot)) {
|
|
8489
8497
|
console.log(`WARN project missing: ${project.name} (${projectRoot})`);
|
|
@@ -8491,6 +8499,12 @@ async function runSeed(config, options) {
|
|
|
8491
8499
|
}
|
|
8492
8500
|
const candidates = await collectSeedCandidates(project, projectRoot, ignorePatterns);
|
|
8493
8501
|
for (const candidate of candidates) {
|
|
8502
|
+
const fileStat = await statSeedFile(candidate.filePath);
|
|
8503
|
+
const recorded = state.files[candidate.destinationUri];
|
|
8504
|
+
if (fileStat && recorded && recorded.mtimeMs === fileStat.mtimeMs && recorded.size === fileStat.size) {
|
|
8505
|
+
unchangedCount += 1;
|
|
8506
|
+
continue;
|
|
8507
|
+
}
|
|
8494
8508
|
const importPath = await prepareSeedFile(config, candidate, options.dryRun === true);
|
|
8495
8509
|
if (!importPath) {
|
|
8496
8510
|
skippedCount += 1;
|
|
@@ -8499,9 +8513,61 @@ async function runSeed(config, options) {
|
|
|
8499
8513
|
const args = withIdentity(config, ["add-resource", importPath, "--to", candidate.destinationUri, "--wait"]);
|
|
8500
8514
|
await maybeRun(options.dryRun === true, ov, args);
|
|
8501
8515
|
importedCount += 1;
|
|
8516
|
+
if (fileStat && options.dryRun !== true) {
|
|
8517
|
+
state.files[candidate.destinationUri] = { mtimeMs: fileStat.mtimeMs, size: fileStat.size };
|
|
8518
|
+
}
|
|
8519
|
+
}
|
|
8520
|
+
}
|
|
8521
|
+
if (options.dryRun !== true) {
|
|
8522
|
+
await writeSeedState(statePath, state);
|
|
8523
|
+
}
|
|
8524
|
+
console.log(
|
|
8525
|
+
`Seed complete: ${importedCount} candidate(s), ${unchangedCount} unchanged, ${skippedCount} skipped for safety.`
|
|
8526
|
+
);
|
|
8527
|
+
}
|
|
8528
|
+
function filterProjects(projects, only) {
|
|
8529
|
+
if (!only || only.length === 0) {
|
|
8530
|
+
return projects;
|
|
8531
|
+
}
|
|
8532
|
+
const known = new Set(projects.map((project) => project.name));
|
|
8533
|
+
const missing = only.filter((name) => !known.has(name));
|
|
8534
|
+
if (missing.length > 0) {
|
|
8535
|
+
const all = [...known].join(", ");
|
|
8536
|
+
throw new Error(`Unknown project(s) in --only: ${missing.join(", ")}. Manifest projects: ${all}`);
|
|
8537
|
+
}
|
|
8538
|
+
const want = new Set(only);
|
|
8539
|
+
return projects.filter((project) => want.has(project.name));
|
|
8540
|
+
}
|
|
8541
|
+
async function statSeedFile(path) {
|
|
8542
|
+
try {
|
|
8543
|
+
const result = await (0, import_promises7.stat)(path);
|
|
8544
|
+
return { mtimeMs: result.mtimeMs, size: result.size };
|
|
8545
|
+
} catch (_err) {
|
|
8546
|
+
return void 0;
|
|
8547
|
+
}
|
|
8548
|
+
}
|
|
8549
|
+
async function readSeedState(path) {
|
|
8550
|
+
try {
|
|
8551
|
+
const raw = await (0, import_promises7.readFile)(path, "utf8");
|
|
8552
|
+
const parsed = JSON.parse(raw);
|
|
8553
|
+
if (parsed.version !== 1 || !isJsonObject(parsed.files)) {
|
|
8554
|
+
return { files: {}, version: 1 };
|
|
8555
|
+
}
|
|
8556
|
+
const files = {};
|
|
8557
|
+
for (const [uri, entry] of Object.entries(parsed.files)) {
|
|
8558
|
+
if (isJsonObject(entry) && typeof entry.mtimeMs === "number" && typeof entry.size === "number") {
|
|
8559
|
+
files[uri] = { mtimeMs: entry.mtimeMs, size: entry.size };
|
|
8560
|
+
}
|
|
8502
8561
|
}
|
|
8562
|
+
return { files, version: 1 };
|
|
8563
|
+
} catch (_err) {
|
|
8564
|
+
return { files: {}, version: 1 };
|
|
8503
8565
|
}
|
|
8504
|
-
|
|
8566
|
+
}
|
|
8567
|
+
async function writeSeedState(path, state) {
|
|
8568
|
+
await ensureDirectory((0, import_node_path8.dirname)(path), false);
|
|
8569
|
+
await (0, import_promises7.writeFile)(path, `${JSON.stringify(state, void 0, 2)}
|
|
8570
|
+
`, { encoding: "utf8", mode: 384 });
|
|
8505
8571
|
}
|
|
8506
8572
|
async function runInitManifest(config, options) {
|
|
8507
8573
|
const manifestPath = expandPath(
|
|
@@ -11098,7 +11164,12 @@ async function main() {
|
|
|
11098
11164
|
).option("--preserve-memories", "Preserve THREADNOTE_HOME and OpenViking memories (default)").option("--erase-memories", "Delete THREADNOTE_HOME, including all OpenViking memories").action(async (options) => {
|
|
11099
11165
|
await runUninstall(getRuntimeConfig(program2), options);
|
|
11100
11166
|
});
|
|
11101
|
-
program2.command("seed").description("Seed curated context from the manifest; never indexes whole repos by default").option("--dry-run", "Print files and ov commands without importing").option("--manifest <path>", "Manifest path for this seed run").
|
|
11167
|
+
program2.command("seed").description("Seed curated context from the manifest; never indexes whole repos by default").option("--dry-run", "Print files and ov commands without importing").option("--force", "Re-upload every candidate even if mtime+size match the recorded state").option("--manifest <path>", "Manifest path for this seed run").option(
|
|
11168
|
+
"--only <project>",
|
|
11169
|
+
"Restrict seeding to one or more manifest projects by name; repeat for multiple",
|
|
11170
|
+
collectOption,
|
|
11171
|
+
[]
|
|
11172
|
+
).action(async (options) => {
|
|
11102
11173
|
await runSeed(getRuntimeConfig(program2, options.manifest), options);
|
|
11103
11174
|
});
|
|
11104
11175
|
program2.command("init-manifest").description("Create or update a per-developer seed manifest from one or more repo roots").option("--dry-run", "Print the manifest without writing it").option("--path <path>", "Manifest path; defaults to THREADNOTE_MANIFEST or ~/.openviking/seed-manifest.yaml").option("--replace", "Replace the manifest instead of merging with existing projects").option("--repo <path>", "Repo root to include; repeat for multiple repos", collectOption, []).action(async (options) => {
|