@hasna/instructions 0.4.25 → 0.4.27
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/README.md +8 -1
- package/dist/cli/index.js +356 -37
- package/dist/index.js +283 -28
- package/dist/lib/project-context.d.ts +4 -3
- package/dist/lib/project-context.d.ts.map +1 -1
- package/dist/lib/session-render.d.ts +1 -0
- package/dist/lib/session-render.d.ts.map +1 -1
- package/dist/mcp/index.js +25 -4
- package/dist/server/index.js +70 -0
- package/dist/server/openapi.d.ts +97 -0
- package/dist/server/openapi.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -7930,6 +7930,9 @@ function computeProjectContextSourceHash(value) {
|
|
|
7930
7930
|
return `sha256:${sha2562(stableStringify(normalized))}`;
|
|
7931
7931
|
}
|
|
7932
7932
|
function parseProjectContextBundle(input) {
|
|
7933
|
+
return parseProjectContextBundleInternal(input, true, true);
|
|
7934
|
+
}
|
|
7935
|
+
function parseProjectContextBundleInternal(input, allowLegacyHash, normalizeLegacyHash = false) {
|
|
7933
7936
|
let encoded;
|
|
7934
7937
|
try {
|
|
7935
7938
|
const serialized = typeof input === "string" ? input : JSON.stringify(input);
|
|
@@ -7949,7 +7952,7 @@ function parseProjectContextBundle(input) {
|
|
|
7949
7952
|
throw new ProjectContextError("PROJECT_CONTEXT_INVALID", "bundle is not valid JSON");
|
|
7950
7953
|
}
|
|
7951
7954
|
const candidateSchema = isRecord(value) ? value["schema"] : undefined;
|
|
7952
|
-
if (typeof candidateSchema === "string" && candidateSchema
|
|
7955
|
+
if (typeof candidateSchema === "string" && !PROJECT_CONTEXT_SUPPORTED_SCHEMAS.includes(candidateSchema)) {
|
|
7953
7956
|
if (/^hasna\.projects\.project_context_bundle\.v[0-9]+$/.test(candidateSchema)) {
|
|
7954
7957
|
throw new ProjectContextError("PROJECT_CONTEXT_UNSUPPORTED_VERSION", `unsupported bundle schema ${candidateSchema}`);
|
|
7955
7958
|
}
|
|
@@ -7966,10 +7969,11 @@ function parseProjectContextBundle(input) {
|
|
|
7966
7969
|
validateIdentityConsistency(bundle);
|
|
7967
7970
|
rejectCredentialLikeBundle(bundle);
|
|
7968
7971
|
const expected = computeProjectContextSourceHash(bundle);
|
|
7969
|
-
|
|
7972
|
+
const matchesLegacyHash = bundle.hash !== expected && allowLegacyHash && bundle.hash === computeLegacyProjectContextSourceHash(bundle);
|
|
7973
|
+
if (bundle.hash !== expected && !matchesLegacyHash) {
|
|
7970
7974
|
throw new ProjectContextError("PROJECT_CONTEXT_HASH_MISMATCH", "bundle hash does not match its canonical allowlisted payload");
|
|
7971
7975
|
}
|
|
7972
|
-
return bundle;
|
|
7976
|
+
return matchesLegacyHash && normalizeLegacyHash ? { ...bundle, hash: expected } : bundle;
|
|
7973
7977
|
}
|
|
7974
7978
|
function planProjectContext(input) {
|
|
7975
7979
|
const workspaceRoot = assertSafeWorkspaceRoot(input.workspace_root);
|
|
@@ -8217,6 +8221,8 @@ function applyProjectContext(options) {
|
|
|
8217
8221
|
content: `${JSON.stringify(sessionManifest, null, 2)}
|
|
8218
8222
|
`
|
|
8219
8223
|
};
|
|
8224
|
+
const manifestContent = `${JSON.stringify(buildManifest(plan, now3), null, 2)}
|
|
8225
|
+
`;
|
|
8220
8226
|
options.test_hooks?.before_compare?.({ attempt, plan });
|
|
8221
8227
|
if (!hashesStillMatch(plan.expected_hashes, workspaceRoot)) {
|
|
8222
8228
|
if (attempt === 0) {
|
|
@@ -8229,7 +8235,15 @@ function applyProjectContext(options) {
|
|
|
8229
8235
|
return resultForPlan(plan, true, raceRetries, null);
|
|
8230
8236
|
assertWorkspaceLockHeld(lockPath, lock, workspaceRoot);
|
|
8231
8237
|
try {
|
|
8232
|
-
|
|
8238
|
+
const metadataSnapshotPath = writeMetadataSnapshot(plan, now3);
|
|
8239
|
+
const rollbackSnapshotPath = writeProjectContextRollbackSnapshot(plan, now3, [
|
|
8240
|
+
{ path: plan.fragment_path, content: plan.fragment, role: "fragment" },
|
|
8241
|
+
{ path: plan.target_path, content: plan.target_content, role: "index" },
|
|
8242
|
+
{ path: plan.cache_path, content: cacheContent, role: "config" },
|
|
8243
|
+
{ path: sessionOutput.path, content: sessionOutput.content, role: "manifest" },
|
|
8244
|
+
{ path: plan.manifest_path, content: manifestContent, role: "manifest" }
|
|
8245
|
+
]);
|
|
8246
|
+
snapshotPath = rollbackSnapshotPath ?? metadataSnapshotPath;
|
|
8233
8247
|
atomicWriteFile(plan.fragment_path, plan.fragment, workspaceRoot, 420, expectedPlanHash(plan, plan.fragment_path), undefined, options.test_hooks?.atomic_exchange_unavailable, undefined, options.test_hooks?.portable_create_only);
|
|
8234
8248
|
options.test_hooks?.after_fragment?.({ attempt, plan });
|
|
8235
8249
|
assertWorkspaceLockHeld(lockPath, lock, workspaceRoot);
|
|
@@ -8243,9 +8257,7 @@ function applyProjectContext(options) {
|
|
|
8243
8257
|
options.test_hooks?.before_manifest?.({ attempt, plan });
|
|
8244
8258
|
assertWorkspaceLockHeld(lockPath, lock, workspaceRoot);
|
|
8245
8259
|
assertRenderedOutputsStable(plan, cacheContent, sessionOutput);
|
|
8246
|
-
|
|
8247
|
-
atomicWriteFile(plan.manifest_path, `${JSON.stringify(manifest, null, 2)}
|
|
8248
|
-
`, workspaceRoot, 384, expectedPlanHash(plan, plan.manifest_path), undefined, options.test_hooks?.atomic_exchange_unavailable, undefined, options.test_hooks?.portable_create_only);
|
|
8260
|
+
atomicWriteFile(plan.manifest_path, manifestContent, workspaceRoot, 384, expectedPlanHash(plan, plan.manifest_path), undefined, options.test_hooks?.atomic_exchange_unavailable, undefined, options.test_hooks?.portable_create_only);
|
|
8249
8261
|
return resultForPlan(plan, false, raceRetries, snapshotPath);
|
|
8250
8262
|
} catch (error) {
|
|
8251
8263
|
if (error instanceof ProjectContextHashRace && attempt === 0) {
|
|
@@ -8336,10 +8348,12 @@ function resolveBundleForApply(options, workspaceRoot, now3) {
|
|
|
8336
8348
|
if (cache.project_id !== options.expected_project_id || cache.bundle.project.id !== options.expected_project_id) {
|
|
8337
8349
|
throw new ProjectContextError("PROJECT_CONTEXT_CACHE_ID_MISMATCH", "cached project context belongs to a different project");
|
|
8338
8350
|
}
|
|
8339
|
-
const
|
|
8340
|
-
if (
|
|
8351
|
+
const cachedBundle = cache.bundle;
|
|
8352
|
+
if (cachedBundle.revision !== cache.revision || cachedBundle.hash !== cache.hash) {
|
|
8341
8353
|
throw new ProjectContextError("PROJECT_CONTEXT_CACHE_INVALID", "cached revision or hash metadata is inconsistent");
|
|
8342
8354
|
}
|
|
8355
|
+
const canonicalHash = computeProjectContextSourceHash(cachedBundle);
|
|
8356
|
+
const bundle = cachedBundle.hash === canonicalHash ? cachedBundle : { ...cachedBundle, hash: canonicalHash };
|
|
8343
8357
|
const ageSeconds = Math.max(staleCacheAgeInSeconds(bundle.generated_at, now3, "bundle generated_at"), staleCacheAgeInSeconds(cache.cached_at, now3, "cache cached_at"));
|
|
8344
8358
|
const maxAge = normalizeMaxStaleAge(options.max_stale_age_seconds);
|
|
8345
8359
|
if (ageSeconds > maxAge) {
|
|
@@ -8436,11 +8450,13 @@ function parseManagedBlock(content, force) {
|
|
|
8436
8450
|
if (structurallyInvalid) {
|
|
8437
8451
|
if (!force)
|
|
8438
8452
|
throw new ProjectContextError("MANAGED_BLOCK_INVALID", "managed project-context markers are duplicate, nested, malformed, or unbalanced");
|
|
8453
|
+
const firstMarkerRange = markerCommentRange(markerLines[0]);
|
|
8454
|
+
const lastMarkerRange = markerCommentRange(markerLines[markerLines.length - 1]);
|
|
8439
8455
|
return {
|
|
8440
8456
|
block: null,
|
|
8441
8457
|
forceRange: {
|
|
8442
|
-
start: markerLines[0].start,
|
|
8443
|
-
end: lineContentEnd(markerLines[markerLines.length - 1])
|
|
8458
|
+
start: firstMarkerRange?.start ?? markerLines[0].start,
|
|
8459
|
+
end: lastMarkerRange?.end ?? lineContentEnd(markerLines[markerLines.length - 1])
|
|
8444
8460
|
}
|
|
8445
8461
|
};
|
|
8446
8462
|
}
|
|
@@ -8471,6 +8487,21 @@ function parseManagedBlock(content, force) {
|
|
|
8471
8487
|
forceRange: null
|
|
8472
8488
|
};
|
|
8473
8489
|
}
|
|
8490
|
+
function markerCommentRange(line) {
|
|
8491
|
+
const canonicalMarker = line.text.indexOf(PROJECT_CONTEXT_MANAGED_COMMENT);
|
|
8492
|
+
const legacyMarker = /@hasna\/configs project context/i.exec(line.text)?.index ?? -1;
|
|
8493
|
+
const marker = canonicalMarker >= 0 ? canonicalMarker : legacyMarker;
|
|
8494
|
+
if (marker < 0)
|
|
8495
|
+
return null;
|
|
8496
|
+
const commentStart = line.text.lastIndexOf("<!--", marker);
|
|
8497
|
+
const commentEnd = line.text.indexOf("-->", marker);
|
|
8498
|
+
if (commentStart < 0 || commentEnd < 0)
|
|
8499
|
+
return null;
|
|
8500
|
+
return {
|
|
8501
|
+
start: line.start + commentStart,
|
|
8502
|
+
end: line.start + commentEnd + "-->".length
|
|
8503
|
+
};
|
|
8504
|
+
}
|
|
8474
8505
|
function parseMarkerLine(text) {
|
|
8475
8506
|
const line = text.replace(/[\r\n]+$/, "");
|
|
8476
8507
|
const canonical = line.match(/^<!-- Managed by @hasna\/configs project context (BEGIN|END) id=([A-Za-z0-9][A-Za-z0-9._:@+-]*) revision=([A-Za-z0-9%._~+-]+) hash=(sha256:[a-f0-9]{64}) -->$/);
|
|
@@ -8492,8 +8523,14 @@ function parseMarkerLine(text) {
|
|
|
8492
8523
|
}
|
|
8493
8524
|
function replaceOrAppendManagedBlock(content, block, parsed, legacy) {
|
|
8494
8525
|
const range = parsed.block ?? parsed.forceRange ?? legacy;
|
|
8495
|
-
if (range)
|
|
8496
|
-
|
|
8526
|
+
if (range) {
|
|
8527
|
+
const before = content.slice(0, range.start);
|
|
8528
|
+
const after = content.slice(range.end);
|
|
8529
|
+
const eol2 = preferredEol(content);
|
|
8530
|
+
const beforeSeparator = before && !/[\r\n]$/.test(before) ? eol2 : "";
|
|
8531
|
+
const afterSeparator = after && !/^[\r\n]/.test(after) ? eol2 : "";
|
|
8532
|
+
return `${before}${beforeSeparator}${block}${afterSeparator}${after}`;
|
|
8533
|
+
}
|
|
8497
8534
|
if (!content)
|
|
8498
8535
|
return `${block}
|
|
8499
8536
|
`;
|
|
@@ -8537,13 +8574,18 @@ function findLegacyCodewithWorkspaceSection(workspaceRoot, runtime, content, bun
|
|
|
8537
8574
|
}
|
|
8538
8575
|
function assertRevisionOrdering(plan, force) {
|
|
8539
8576
|
const observations = [];
|
|
8577
|
+
const cache = readProjectContextCache(plan.cache_path, plan.workspace_root);
|
|
8578
|
+
const canonicalCacheHash = cache === null ? null : computeProjectContextSourceHash(cache.bundle);
|
|
8579
|
+
const normalizePersistedHash = (revision, hash) => cache !== null && canonicalCacheHash !== null && revision === cache.revision && hash === cache.hash ? canonicalCacheHash : hash;
|
|
8540
8580
|
const manifest = readProjectContextManifest(plan.manifest_path, plan.workspace_root);
|
|
8541
8581
|
if (manifest) {
|
|
8582
|
+
const manifestHashHasRecoveryProof = manifest.projectContext.hash === plan.bundle.hash || metadataSnapshotMatchesManifest(plan, manifest);
|
|
8583
|
+
const canonicalStateAlreadyInstalled = cache !== null && canonicalCacheHash === plan.bundle.hash && cache.project_id === plan.bundle.project.id && cache.revision === plan.bundle.revision && plan.marker !== null && plan.marker.id === plan.bundle.project.id && plan.marker.revision === plan.bundle.revision && plan.marker.hash === plan.bundle.hash && existsSync4(plan.fragment_path) && fragmentMatchesBundle(plan.fragment_path, plan.bundle, plan.workspace_root) && manifestHashHasRecoveryProof;
|
|
8542
8584
|
observations.push({
|
|
8543
8585
|
source: "manifest",
|
|
8544
8586
|
id: manifest.projectContext.projectId,
|
|
8545
8587
|
revision: manifest.projectContext.revision,
|
|
8546
|
-
hash: manifest.projectContext.hash
|
|
8588
|
+
hash: canonicalStateAlreadyInstalled && manifest.projectContext.projectId === plan.bundle.project.id && manifest.projectContext.revision === plan.bundle.revision ? plan.bundle.hash : normalizePersistedHash(manifest.projectContext.revision, manifest.projectContext.hash)
|
|
8547
8589
|
});
|
|
8548
8590
|
const fragmentEntry = manifest.files.find((file) => file.relativePath === PROJECT_CONTEXT_FRAGMENT_PATH);
|
|
8549
8591
|
if (fragmentEntry && existsSync4(plan.fragment_path)) {
|
|
@@ -8553,11 +8595,16 @@ function assertRevisionOrdering(plan, force) {
|
|
|
8553
8595
|
}
|
|
8554
8596
|
}
|
|
8555
8597
|
}
|
|
8556
|
-
const cache = readProjectContextCache(plan.cache_path, plan.workspace_root);
|
|
8557
8598
|
if (cache)
|
|
8558
|
-
observations.push({ source: "cache", id: cache.project_id, revision: cache.revision, hash:
|
|
8559
|
-
if (plan.marker)
|
|
8560
|
-
observations.push({
|
|
8599
|
+
observations.push({ source: "cache", id: cache.project_id, revision: cache.revision, hash: canonicalCacheHash });
|
|
8600
|
+
if (plan.marker) {
|
|
8601
|
+
observations.push({
|
|
8602
|
+
source: "marker",
|
|
8603
|
+
id: plan.marker.id,
|
|
8604
|
+
revision: plan.marker.revision,
|
|
8605
|
+
hash: normalizePersistedHash(plan.marker.revision, plan.marker.hash)
|
|
8606
|
+
});
|
|
8607
|
+
}
|
|
8561
8608
|
for (const observation of observations) {
|
|
8562
8609
|
if (observation.id !== plan.bundle.project.id) {
|
|
8563
8610
|
throw new ProjectContextError("PROJECT_CONTEXT_IDENTITY_CONFLICT", `${observation.source} belongs to another project`);
|
|
@@ -8677,6 +8724,10 @@ function buildSessionCompatibilityManifest(plan, now3) {
|
|
|
8677
8724
|
};
|
|
8678
8725
|
const targetOwner = isRecord(existing["targetOwner"]) ? existing["targetOwner"] : {};
|
|
8679
8726
|
const adapterMode = plan.native_imports ? "native-imports" : "flattened-markdown";
|
|
8727
|
+
const existingProjectContext = isRecord(existing["projectContext"]) ? existing["projectContext"] : null;
|
|
8728
|
+
const existingProjectContextHash = existingProjectContext === null ? null : safeLegacyMetadataString(existingProjectContext["hash"], null);
|
|
8729
|
+
const existingSourceHash = typeof existing["sourceHash"] === "string" ? existing["sourceHash"] : null;
|
|
8730
|
+
const sourceHash = existingProjectContextHash === plan.bundle.hash && existingSourceHash !== null ? existingSourceHash : sha2562(stableStringify({ previous: existingSourceHash, projectContext: plan.bundle.hash }));
|
|
8680
8731
|
return credentialSafeSessionManifest({
|
|
8681
8732
|
schema: SESSION_RENDER_SCHEMA,
|
|
8682
8733
|
tool,
|
|
@@ -8700,7 +8751,7 @@ function buildSessionCompatibilityManifest(plan, now3) {
|
|
|
8700
8751
|
blockers: [],
|
|
8701
8752
|
generatedAt: now3.toISOString(),
|
|
8702
8753
|
env: sanitizeLegacyEnvironment(existing["env"]),
|
|
8703
|
-
sourceHash
|
|
8754
|
+
sourceHash,
|
|
8704
8755
|
sources,
|
|
8705
8756
|
skippedSources: sanitizeLegacySkippedSources(existing["skippedSources"]),
|
|
8706
8757
|
files: [...files.filter((file) => file["relativePath"] !== targetRelativePath), updatedTarget],
|
|
@@ -8905,7 +8956,7 @@ function projectContextManifestSource(cachePath, runtime, bundle) {
|
|
|
8905
8956
|
rules: [],
|
|
8906
8957
|
renderedPayloadSha256: sha2562(JSON.stringify(bundle)),
|
|
8907
8958
|
provenance: {
|
|
8908
|
-
schema:
|
|
8959
|
+
schema: bundle.schema,
|
|
8909
8960
|
projectId: bundle.project.id,
|
|
8910
8961
|
revision: bundle.revision,
|
|
8911
8962
|
hash: bundle.hash
|
|
@@ -8914,7 +8965,7 @@ function projectContextManifestSource(cachePath, runtime, bundle) {
|
|
|
8914
8965
|
}
|
|
8915
8966
|
function manifestProjectContext(plan) {
|
|
8916
8967
|
return {
|
|
8917
|
-
schema:
|
|
8968
|
+
schema: plan.bundle.schema,
|
|
8918
8969
|
projectId: plan.bundle.project.id,
|
|
8919
8970
|
revision: plan.bundle.revision,
|
|
8920
8971
|
hash: plan.bundle.hash,
|
|
@@ -8956,6 +9007,77 @@ function writeMetadataSnapshot(plan, now3) {
|
|
|
8956
9007
|
`, plan.workspace_root, 384);
|
|
8957
9008
|
return snapshotPath;
|
|
8958
9009
|
}
|
|
9010
|
+
function metadataSnapshotMatchesManifest(plan, manifest) {
|
|
9011
|
+
const snapshotDir = resolve(plan.workspace_root, ...PROJECT_CONTEXT_SNAPSHOT_DIR.split("/"));
|
|
9012
|
+
const snapshotPath = resolve(snapshotDir, `${safeFilename(manifest.projectContext.revision)}-${manifest.projectContext.hash.slice(-12)}.json`);
|
|
9013
|
+
if (!existsSync4(snapshotPath))
|
|
9014
|
+
return false;
|
|
9015
|
+
const record = readJsonRecord(snapshotPath, plan.workspace_root);
|
|
9016
|
+
const result = projectContextMetadataSnapshotSchema.safeParse(record);
|
|
9017
|
+
if (!result.success)
|
|
9018
|
+
return false;
|
|
9019
|
+
if (result.data.projectId !== manifest.projectContext.projectId || result.data.revision !== manifest.projectContext.revision || result.data.hash !== manifest.projectContext.hash || result.data.status !== manifest.projectContext.status)
|
|
9020
|
+
return false;
|
|
9021
|
+
const sortFiles = (files) => [...files].sort((left, right) => left.relativePath.localeCompare(right.relativePath));
|
|
9022
|
+
const snapshotFiles = sortFiles(result.data.files);
|
|
9023
|
+
const manifestFiles = sortFiles(manifest.files.map((file) => ({
|
|
9024
|
+
relativePath: file.relativePath,
|
|
9025
|
+
role: file.role,
|
|
9026
|
+
sha256: file.sha256
|
|
9027
|
+
})));
|
|
9028
|
+
return JSON.stringify(snapshotFiles) === JSON.stringify(manifestFiles);
|
|
9029
|
+
}
|
|
9030
|
+
function writeProjectContextRollbackSnapshot(plan, now3, outputs) {
|
|
9031
|
+
const targetExpectedHash = expectedPlanHash(plan, plan.target_path);
|
|
9032
|
+
if (targetExpectedHash === sha2562(plan.target_content))
|
|
9033
|
+
return null;
|
|
9034
|
+
const files = outputs.flatMap((output) => {
|
|
9035
|
+
const expectedHash = expectedPlanHash(plan, output.path);
|
|
9036
|
+
if (expectedHash === null)
|
|
9037
|
+
return [];
|
|
9038
|
+
const content = readUtf8RegularFile(output.path, plan.workspace_root, managedObservationMaxBytes(relativePosix(plan.workspace_root, output.path)));
|
|
9039
|
+
if (sha2562(content) !== expectedHash) {
|
|
9040
|
+
throw new ProjectContextHashRace(`managed path changed while creating rollback evidence: ${relativePosix(plan.workspace_root, output.path)}`);
|
|
9041
|
+
}
|
|
9042
|
+
return [{
|
|
9043
|
+
path: output.path,
|
|
9044
|
+
relativePath: relativePosix(plan.workspace_root, output.path),
|
|
9045
|
+
role: output.role,
|
|
9046
|
+
sha256: expectedHash,
|
|
9047
|
+
content
|
|
9048
|
+
}];
|
|
9049
|
+
});
|
|
9050
|
+
const afterFiles = outputs.map((output) => {
|
|
9051
|
+
const previousHash = expectedPlanHash(plan, output.path);
|
|
9052
|
+
const nextHash = sha2562(output.content);
|
|
9053
|
+
return {
|
|
9054
|
+
path: output.path,
|
|
9055
|
+
relativePath: relativePosix(plan.workspace_root, output.path),
|
|
9056
|
+
role: output.role,
|
|
9057
|
+
action: previousHash === null ? "create" : previousHash === nextHash ? "unchanged" : "update",
|
|
9058
|
+
sha256: nextHash
|
|
9059
|
+
};
|
|
9060
|
+
});
|
|
9061
|
+
const snapshotDir = resolve(plan.workspace_root, ...SESSION_RENDER_SNAPSHOT_RELATIVE_DIR.split("/"));
|
|
9062
|
+
ensureSafeDirectory(snapshotDir, plan.workspace_root, 448);
|
|
9063
|
+
const timestamp = now3.toISOString().replace(/[:.]/g, "-");
|
|
9064
|
+
const snapshotPath = resolve(snapshotDir, `${timestamp}-${randomUUID6()}.json`);
|
|
9065
|
+
const snapshot = {
|
|
9066
|
+
schema: "hasna.configs.session-render-snapshot/v2",
|
|
9067
|
+
createdAt: now3.toISOString(),
|
|
9068
|
+
tool: manifestTool(plan.runtime),
|
|
9069
|
+
profile: "project-context",
|
|
9070
|
+
targetHome: plan.workspace_root,
|
|
9071
|
+
targetKind: "project-root",
|
|
9072
|
+
manifestPath: plan.manifest_path,
|
|
9073
|
+
previousManifest: null,
|
|
9074
|
+
files,
|
|
9075
|
+
afterFiles
|
|
9076
|
+
};
|
|
9077
|
+
atomicWriteFile(snapshotPath, `${JSON.stringify(snapshot, null, 2)}
|
|
9078
|
+
`, plan.workspace_root, 384, null);
|
|
9079
|
+
return snapshotPath;
|
|
9080
|
+
}
|
|
8959
9081
|
function readProjectContextManifest(path, workspaceRoot) {
|
|
8960
9082
|
if (!existsSync4(path))
|
|
8961
9083
|
return null;
|
|
@@ -8979,7 +9101,7 @@ function readProjectContextCache(path, workspaceRoot) {
|
|
|
8979
9101
|
if (!result.success) {
|
|
8980
9102
|
throw new ProjectContextError("PROJECT_CONTEXT_CACHE_INVALID", "cache is malformed or incompatible");
|
|
8981
9103
|
}
|
|
8982
|
-
const bundle =
|
|
9104
|
+
const bundle = parseProjectContextBundleInternal(result.data.bundle, true);
|
|
8983
9105
|
if (result.data.project_id !== bundle.project.id || result.data.revision !== bundle.revision || result.data.hash !== bundle.hash) {
|
|
8984
9106
|
throw new ProjectContextError("PROJECT_CONTEXT_CACHE_INVALID", "cache metadata does not match its bundle");
|
|
8985
9107
|
}
|
|
@@ -10292,23 +10414,36 @@ function removeHashForFingerprint(value) {
|
|
|
10292
10414
|
return value;
|
|
10293
10415
|
const copy = {};
|
|
10294
10416
|
for (const [key, item] of Object.entries(value)) {
|
|
10295
|
-
if (key === "hash")
|
|
10417
|
+
if (key === "generated_at" || key === "hash")
|
|
10296
10418
|
continue;
|
|
10297
10419
|
copy[key] = item;
|
|
10298
10420
|
}
|
|
10299
10421
|
return copy;
|
|
10300
10422
|
}
|
|
10423
|
+
function computeLegacyProjectContextSourceHash(value) {
|
|
10424
|
+
if (!isRecord(value))
|
|
10425
|
+
return `sha256:${sha2562(stableStringify(value))}`;
|
|
10426
|
+
const copy = {};
|
|
10427
|
+
for (const [key, item] of Object.entries(value)) {
|
|
10428
|
+
if (key === "hash")
|
|
10429
|
+
continue;
|
|
10430
|
+
copy[key] = item;
|
|
10431
|
+
}
|
|
10432
|
+
return `sha256:${sha2562(stableStringify(copy))}`;
|
|
10433
|
+
}
|
|
10301
10434
|
function sha2562(content) {
|
|
10302
10435
|
return createHash2("sha256").update(content).digest("hex");
|
|
10303
10436
|
}
|
|
10304
10437
|
function isRecord(value) {
|
|
10305
10438
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
10306
10439
|
}
|
|
10307
|
-
var PROJECT_CONTEXT_SCHEMA = "hasna.projects.project_context_bundle.v1", PROJECT_CONTEXT_MAX_INPUT_BYTES, PROJECT_CONTEXT_MAX_RENDERED_BYTES, PROJECT_CONTEXT_MAX_APPROX_TOKENS = 1000, PROJECT_CONTEXT_MAX_COMMANDS = 6, PROJECT_CONTEXT_MAX_WARNINGS = 3, PROJECT_CONTEXT_FRAGMENT_PATH = ".hasna/instructions/project-context.md", PROJECT_CONTEXT_MANIFEST_PATH = ".hasna/project-context-manifest.json", PROJECT_CONTEXT_CACHE_PATH = ".hasna/project-context-cache.json", PROJECT_CONTEXT_LOCK_PATH = ".hasna/project-context.lock", PROJECT_CONTEXT_SNAPSHOT_DIR = ".hasna/project-context-snapshots", PROJECT_CONTEXT_CACHE_SCHEMA = "hasna.instructions.project-context-cache/v1", PROJECT_CONTEXT_MANAGED_COMMENT = "Managed by @hasna/configs project context", SESSION_COMPATIBILITY_MANIFEST_MAX_BYTES, FOREIGN_INPUT_MAX_BYTES, SESSION_MANAGED_OUTPUT_MAX_BYTES, SESSION_MANAGED_INPUT_MAX_BYTES, SESSION_MANAGED_OUTPUT_PATHS, SESSION_MANAGED_OUTPUT_WARN_BYTES, PROJECT_CONTEXT_LOCK_STALE_MS, LEGACY_CONFIGS_PACKAGE = "@hasna/configs", LEGACY_CONFIGS_COMPAT_VERSION = "0.2.45", LEGACY_CONFIGS_EXECUTABLE = "configs", PROJECT_KINDS, PROJECT_STATUSES, LINK_STATES, RESOLUTION_SOURCES, safeId, nullableId, producerSlug, producerName, safeOptionalDisplay, isoTimestamp, revisionSchema, hashSchema, absolutePath, commandArg, commandSchema, projectContextBundleSchema, storedManifestProjectContextSchema, storedManifestFileSchema, storedManifestObservationSchema, projectContextCacheSchema, ProjectContextError, ProjectContextHashRace, anchoredFsOps, atomicExchange, atomicExchangeLibraries;
|
|
10440
|
+
var PROJECT_CONTEXT_SCHEMA = "hasna.projects.project_context_bundle.v1", PROJECT_CONTEXT_SCHEMA_V2 = "hasna.projects.project_context_bundle.v2", PROJECT_CONTEXT_SUPPORTED_SCHEMAS, projectContextSchema, PROJECT_CONTEXT_MAX_INPUT_BYTES, PROJECT_CONTEXT_MAX_RENDERED_BYTES, PROJECT_CONTEXT_MAX_APPROX_TOKENS = 1000, PROJECT_CONTEXT_MAX_COMMANDS = 6, PROJECT_CONTEXT_MAX_WARNINGS = 3, PROJECT_CONTEXT_FRAGMENT_PATH = ".hasna/instructions/project-context.md", PROJECT_CONTEXT_MANIFEST_PATH = ".hasna/project-context-manifest.json", PROJECT_CONTEXT_CACHE_PATH = ".hasna/project-context-cache.json", PROJECT_CONTEXT_LOCK_PATH = ".hasna/project-context.lock", PROJECT_CONTEXT_SNAPSHOT_DIR = ".hasna/project-context-snapshots", PROJECT_CONTEXT_CACHE_SCHEMA = "hasna.instructions.project-context-cache/v1", PROJECT_CONTEXT_MANAGED_COMMENT = "Managed by @hasna/configs project context", SESSION_COMPATIBILITY_MANIFEST_MAX_BYTES, FOREIGN_INPUT_MAX_BYTES, SESSION_MANAGED_OUTPUT_MAX_BYTES, SESSION_MANAGED_INPUT_MAX_BYTES, SESSION_MANAGED_OUTPUT_PATHS, SESSION_MANAGED_OUTPUT_WARN_BYTES, PROJECT_CONTEXT_LOCK_STALE_MS, LEGACY_CONFIGS_PACKAGE = "@hasna/configs", LEGACY_CONFIGS_COMPAT_VERSION = "0.2.45", LEGACY_CONFIGS_EXECUTABLE = "configs", PROJECT_KINDS, PROJECT_STATUSES, LINK_STATES, RESOLUTION_SOURCES, safeId, nullableId, producerSlug, producerName, safeOptionalDisplay, isoTimestamp, revisionSchema, hashSchema, absolutePath, commandArg, commandSchema, projectContextBundleSchema, storedManifestProjectContextSchema, storedManifestFileSchema, storedManifestObservationSchema, projectContextMetadataSnapshotSchema, projectContextCacheSchema, ProjectContextError, ProjectContextHashRace, anchoredFsOps, atomicExchange, atomicExchangeLibraries;
|
|
10308
10441
|
var init_project_context = __esm(() => {
|
|
10309
10442
|
init_zod();
|
|
10310
10443
|
init_redact();
|
|
10311
10444
|
init_session_render_contract();
|
|
10445
|
+
PROJECT_CONTEXT_SUPPORTED_SCHEMAS = [PROJECT_CONTEXT_SCHEMA, PROJECT_CONTEXT_SCHEMA_V2];
|
|
10446
|
+
projectContextSchema = exports_external.enum(PROJECT_CONTEXT_SUPPORTED_SCHEMAS);
|
|
10312
10447
|
PROJECT_CONTEXT_MAX_INPUT_BYTES = 8 * 1024;
|
|
10313
10448
|
PROJECT_CONTEXT_MAX_RENDERED_BYTES = 4 * 1024;
|
|
10314
10449
|
SESSION_COMPATIBILITY_MANIFEST_MAX_BYTES = 8 * 1024 * 1024;
|
|
@@ -10356,7 +10491,7 @@ var init_project_context = __esm(() => {
|
|
|
10356
10491
|
argv: exports_external.array(commandArg).min(1).max(8)
|
|
10357
10492
|
}).strict();
|
|
10358
10493
|
projectContextBundleSchema = exports_external.object({
|
|
10359
|
-
schema:
|
|
10494
|
+
schema: projectContextSchema,
|
|
10360
10495
|
generated_at: isoTimestamp,
|
|
10361
10496
|
hash: hashSchema,
|
|
10362
10497
|
revision: revisionSchema,
|
|
@@ -10404,7 +10539,7 @@ var init_project_context = __esm(() => {
|
|
|
10404
10539
|
commands: exports_external.array(commandSchema).max(PROJECT_CONTEXT_MAX_COMMANDS)
|
|
10405
10540
|
}).strict();
|
|
10406
10541
|
storedManifestProjectContextSchema = exports_external.object({
|
|
10407
|
-
schema:
|
|
10542
|
+
schema: projectContextSchema,
|
|
10408
10543
|
projectId: safeId,
|
|
10409
10544
|
revision: revisionSchema,
|
|
10410
10545
|
hash: hashSchema,
|
|
@@ -10444,6 +10579,25 @@ var init_project_context = __esm(() => {
|
|
|
10444
10579
|
});
|
|
10445
10580
|
}
|
|
10446
10581
|
});
|
|
10582
|
+
projectContextMetadataSnapshotSchema = exports_external.object({
|
|
10583
|
+
schema: exports_external.literal("hasna.configs.session-render-snapshot/v1"),
|
|
10584
|
+
kind: exports_external.literal("project-context-metadata"),
|
|
10585
|
+
createdAt: isoTimestamp,
|
|
10586
|
+
projectId: safeId,
|
|
10587
|
+
revision: revisionSchema,
|
|
10588
|
+
hash: hashSchema,
|
|
10589
|
+
status: exports_external.enum(["fresh", "stale-source", "stale-cache"]),
|
|
10590
|
+
files: exports_external.array(exports_external.object({
|
|
10591
|
+
relativePath: exports_external.enum([
|
|
10592
|
+
PROJECT_CONTEXT_FRAGMENT_PATH,
|
|
10593
|
+
"CLAUDE.md",
|
|
10594
|
+
".codewith/CODEWITH.md",
|
|
10595
|
+
"AGENTS.md"
|
|
10596
|
+
]),
|
|
10597
|
+
role: exports_external.enum(["fragment", "index"]),
|
|
10598
|
+
sha256: exports_external.string().regex(/^[a-f0-9]{64}$/)
|
|
10599
|
+
}).strict()).min(1).max(2)
|
|
10600
|
+
}).strict();
|
|
10447
10601
|
projectContextCacheSchema = exports_external.object({
|
|
10448
10602
|
schema: exports_external.literal(PROJECT_CONTEXT_CACHE_SCHEMA),
|
|
10449
10603
|
cached_at: isoTimestamp,
|
|
@@ -10656,10 +10810,13 @@ function sourceFingerprint(source) {
|
|
|
10656
10810
|
metadata: canonicalFingerprintValue(source.metadata ?? null)
|
|
10657
10811
|
};
|
|
10658
10812
|
}
|
|
10659
|
-
function
|
|
10813
|
+
function normalizeSessionInstructionSourceId(value) {
|
|
10660
10814
|
const s = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
10661
10815
|
return s || "instruction";
|
|
10662
10816
|
}
|
|
10817
|
+
function slug(value) {
|
|
10818
|
+
return normalizeSessionInstructionSourceId(value);
|
|
10819
|
+
}
|
|
10663
10820
|
function yamlQuote2(value) {
|
|
10664
10821
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
10665
10822
|
}
|
|
@@ -10778,12 +10935,17 @@ function normalizeSources(sources, tool, allowEmptySources) {
|
|
|
10778
10935
|
}
|
|
10779
10936
|
return normalized2;
|
|
10780
10937
|
});
|
|
10938
|
+
const originalOrder = [...normalized].sort(compareSessionInstructionSources);
|
|
10781
10939
|
const deduplicated = deduplicateSemanticPolicySources(normalized);
|
|
10782
|
-
const ordered = deduplicated.selected.sort(
|
|
10940
|
+
const ordered = deduplicated.selected.sort(compareSessionInstructionSources);
|
|
10941
|
+
validateTargetedReplacementSources(originalOrder, ordered, deduplicated.skipped);
|
|
10783
10942
|
rejectDuplicateSourceSlugs(ordered);
|
|
10784
10943
|
rejectDuplicateRulePaths(ordered);
|
|
10785
10944
|
return { sources: ordered, skipped: deduplicated.skipped };
|
|
10786
10945
|
}
|
|
10946
|
+
function compareSessionInstructionSources(a, b) {
|
|
10947
|
+
return SESSION_LAYER_RANK[a.resolvedLayer] - SESSION_LAYER_RANK[b.resolvedLayer] || a.resolvedOrder - b.resolvedOrder || a.id.localeCompare(b.id);
|
|
10948
|
+
}
|
|
10787
10949
|
function semanticPolicyIntegrity(body) {
|
|
10788
10950
|
return sha2563(body) === AGENT_OPERATING_RULES_PAYLOAD_SHA256 ? "pinned-digest" : "unverified-self-declared";
|
|
10789
10951
|
}
|
|
@@ -10910,7 +11072,52 @@ function filterProviderOnlyBlocks(content, tool) {
|
|
|
10910
11072
|
return output.join(`
|
|
10911
11073
|
`);
|
|
10912
11074
|
}
|
|
10913
|
-
function
|
|
11075
|
+
function targetedReplacementTarget(source) {
|
|
11076
|
+
if (source.replacementScope == null)
|
|
11077
|
+
return null;
|
|
11078
|
+
if (source.resolvedMerge !== "replace") {
|
|
11079
|
+
throw new Error(`Session instruction source "${source.id}" uses replacement scope "${source.replacementScope}" ` + `with merge=${source.resolvedMerge}; replacement scopes require merge=replace, not append.`);
|
|
11080
|
+
}
|
|
11081
|
+
const scope = source.replacementScope.trim();
|
|
11082
|
+
if (!scope.startsWith("source:")) {
|
|
11083
|
+
throw new Error(`Invalid replacement scope "${source.replacementScope}" for source "${source.id}"; ` + 'expected "source:<normalized-source-id>".');
|
|
11084
|
+
}
|
|
11085
|
+
const target = scope.slice("source:".length);
|
|
11086
|
+
if (!target || target !== slug(target)) {
|
|
11087
|
+
throw new Error(`Invalid replacement scope "${source.replacementScope}" for source "${source.id}"; ` + "the target must be a non-empty normalized source id.");
|
|
11088
|
+
}
|
|
11089
|
+
return target;
|
|
11090
|
+
}
|
|
11091
|
+
function validateTargetedReplacementSources(originalSources, selectedSources, deduplicatedSources) {
|
|
11092
|
+
for (let replacerIndex = 0;replacerIndex < originalSources.length; replacerIndex++) {
|
|
11093
|
+
const replacer = originalSources[replacerIndex];
|
|
11094
|
+
const targetNormalizedId = targetedReplacementTarget(replacer);
|
|
11095
|
+
if (targetNormalizedId === null)
|
|
11096
|
+
continue;
|
|
11097
|
+
const matches = originalSources.filter((source) => source.normalizedId === targetNormalizedId);
|
|
11098
|
+
if (matches.length === 0) {
|
|
11099
|
+
throw new Error(`Targeted replacement source "${replacer.id}" names missing source "${targetNormalizedId}".`);
|
|
11100
|
+
}
|
|
11101
|
+
if (matches.length > 1) {
|
|
11102
|
+
throw new Error(`Targeted replacement source "${replacer.id}" is ambiguous after normalization: ` + `"${targetNormalizedId}" matches ${matches.map((source) => `"${source.id}"`).join(", ")}.`);
|
|
11103
|
+
}
|
|
11104
|
+
const target = matches[0];
|
|
11105
|
+
const targetIndex = originalSources.indexOf(target);
|
|
11106
|
+
if (targetIndex >= replacerIndex) {
|
|
11107
|
+
throw new Error(`Targeted replacement source "${replacer.id}" must name an earlier source; ` + `"${target.id}" is later than or identical to the replacer.`);
|
|
11108
|
+
}
|
|
11109
|
+
if (target.nonOverridable) {
|
|
11110
|
+
throw new Error(`Targeted replacement source "${replacer.id}" cannot replace non-overridable source "${target.id}".`);
|
|
11111
|
+
}
|
|
11112
|
+
if (!selectedSources.some((source) => source.id === replacer.id)) {
|
|
11113
|
+
throw new Error(`Targeted replacement source "${replacer.id}" was removed by semantic-policy deduplication before composition.`);
|
|
11114
|
+
}
|
|
11115
|
+
if (deduplicatedSources.some((source) => source.id === target.id)) {
|
|
11116
|
+
throw new Error(`Targeted replacement source "${replacer.id}" cannot claim success because target "${target.id}" ` + "was removed by semantic-policy deduplication before composition.");
|
|
11117
|
+
}
|
|
11118
|
+
}
|
|
11119
|
+
}
|
|
11120
|
+
function composeBroadReplaceSources(sources) {
|
|
10914
11121
|
let start = -1;
|
|
10915
11122
|
for (let i = 0;i < sources.length; i++) {
|
|
10916
11123
|
if (sources[i].resolvedMerge === "replace")
|
|
@@ -10924,6 +11131,53 @@ function composeSources(sources) {
|
|
|
10924
11131
|
const skipped = earlier.filter((source) => !source.nonOverridable).map((source) => skippedSource(source, `superseded by "${replacer.id}": a replace-merge source discards earlier overridable instruction layers`));
|
|
10925
11132
|
return { sources: [...protectedSources, ...sources.slice(start)], skipped };
|
|
10926
11133
|
}
|
|
11134
|
+
function composeSources(sources) {
|
|
11135
|
+
const hasTargetedReplacement = sources.some((source) => source.replacementScope !== undefined);
|
|
11136
|
+
if (!hasTargetedReplacement)
|
|
11137
|
+
return composeBroadReplaceSources(sources);
|
|
11138
|
+
const selected = [];
|
|
11139
|
+
const skipped = [];
|
|
11140
|
+
for (const source of sources) {
|
|
11141
|
+
const targetNormalizedId = targetedReplacementTarget(source);
|
|
11142
|
+
if (targetNormalizedId !== null) {
|
|
11143
|
+
const targetIndex = selected.findIndex((candidate) => candidate.normalizedId === targetNormalizedId);
|
|
11144
|
+
if (targetIndex < 0) {
|
|
11145
|
+
throw new Error(`Targeted replacement source "${source.id}" cannot replace "${targetNormalizedId}": ` + "the earlier target was already removed.");
|
|
11146
|
+
}
|
|
11147
|
+
const target = selected[targetIndex];
|
|
11148
|
+
if (target.nonOverridable) {
|
|
11149
|
+
throw new Error(`Targeted replacement source "${source.id}" cannot replace non-overridable source "${target.id}".`);
|
|
11150
|
+
}
|
|
11151
|
+
selected.splice(targetIndex, 1);
|
|
11152
|
+
skipped.push(skippedSource(target, `superseded by "${source.id}": targeted replacement ${source.replacementScope} ` + `removed exactly source "${target.id}"`));
|
|
11153
|
+
selected.push({
|
|
11154
|
+
...source,
|
|
11155
|
+
provenance: {
|
|
11156
|
+
...source.provenance ?? {},
|
|
11157
|
+
targetedReplacement: {
|
|
11158
|
+
scope: source.replacementScope,
|
|
11159
|
+
targetSourceId: target.id,
|
|
11160
|
+
targetNormalizedSourceId: target.normalizedId
|
|
11161
|
+
}
|
|
11162
|
+
}
|
|
11163
|
+
});
|
|
11164
|
+
continue;
|
|
11165
|
+
}
|
|
11166
|
+
if (source.resolvedMerge === "replace") {
|
|
11167
|
+
const retained = selected.filter((candidate) => candidate.nonOverridable);
|
|
11168
|
+
for (const candidate of selected) {
|
|
11169
|
+
if (candidate.nonOverridable)
|
|
11170
|
+
continue;
|
|
11171
|
+
skipped.push(skippedSource(candidate, `superseded by "${source.id}": a replace-merge source discards earlier overridable instruction layers`));
|
|
11172
|
+
}
|
|
11173
|
+
selected.length = 0;
|
|
11174
|
+
selected.push(...retained, source);
|
|
11175
|
+
continue;
|
|
11176
|
+
}
|
|
11177
|
+
selected.push(source);
|
|
11178
|
+
}
|
|
11179
|
+
return { sources: selected, skipped };
|
|
11180
|
+
}
|
|
10927
11181
|
function sectionForSource(source) {
|
|
10928
11182
|
const parts = [
|
|
10929
11183
|
`<!-- ${SESSION_RENDER_MANAGED_MARKER}. Do not edit this generated file directly. -->`,
|
|
@@ -15764,7 +16018,7 @@ function parseSessionLayer(value) {
|
|
|
15764
16018
|
return value;
|
|
15765
16019
|
throw new Error(`Invalid source layer "${value}"`);
|
|
15766
16020
|
}
|
|
15767
|
-
function parseSessionSource(value, order
|
|
16021
|
+
function parseSessionSource(value, order) {
|
|
15768
16022
|
const idx = value.indexOf("=");
|
|
15769
16023
|
let id = idx > 0 ? value.slice(0, idx).trim() : "";
|
|
15770
16024
|
const path = idx > 0 ? value.slice(idx + 1).trim() : value.trim();
|
|
@@ -15787,9 +16041,39 @@ function parseSessionSource(value, order, replaceIds) {
|
|
|
15787
16041
|
id: resolvedId,
|
|
15788
16042
|
label: id ? resolvedId : source.label ?? resolvedId,
|
|
15789
16043
|
layer,
|
|
15790
|
-
merge:
|
|
16044
|
+
merge: "append"
|
|
15791
16045
|
};
|
|
15792
16046
|
}
|
|
16047
|
+
function parseSessionSourceReplacement(value) {
|
|
16048
|
+
const trimmed = value.trim();
|
|
16049
|
+
const separator = trimmed.indexOf("=");
|
|
16050
|
+
const replacerId = (separator >= 0 ? trimmed.slice(0, separator) : trimmed).trim();
|
|
16051
|
+
if (!replacerId) {
|
|
16052
|
+
throw new Error(`Invalid --replace-source "${value}" (expected replacer-id or replacer-id=target-source-id)`);
|
|
16053
|
+
}
|
|
16054
|
+
if (separator < 0)
|
|
16055
|
+
return { replacerId };
|
|
16056
|
+
const targetId = trimmed.slice(separator + 1).trim();
|
|
16057
|
+
if (!targetId) {
|
|
16058
|
+
throw new Error(`Invalid --replace-source "${value}" (target source id is required after "=")`);
|
|
16059
|
+
}
|
|
16060
|
+
return {
|
|
16061
|
+
replacerId,
|
|
16062
|
+
replacementScope: `source:${normalizeSessionInstructionSourceId(targetId)}`
|
|
16063
|
+
};
|
|
16064
|
+
}
|
|
16065
|
+
function sessionSourceReplacements(values) {
|
|
16066
|
+
const replacements = new Map;
|
|
16067
|
+
for (const value of values) {
|
|
16068
|
+
const replacement = parseSessionSourceReplacement(value);
|
|
16069
|
+
const existing = replacements.get(replacement.replacerId);
|
|
16070
|
+
if (existing && existing.replacementScope !== replacement.replacementScope) {
|
|
16071
|
+
throw new Error(`Conflicting --replace-source values for "${replacement.replacerId}": ` + `${existing.replacementScope ?? "broad"} and ${replacement.replacementScope ?? "broad"}.`);
|
|
16072
|
+
}
|
|
16073
|
+
replacements.set(replacement.replacerId, replacement);
|
|
16074
|
+
}
|
|
16075
|
+
return replacements;
|
|
16076
|
+
}
|
|
15793
16077
|
function readSessionInstructionSourceFile(path) {
|
|
15794
16078
|
const stat = lstatSync4(path);
|
|
15795
16079
|
if (stat.isSymbolicLink()) {
|
|
@@ -15820,8 +16104,8 @@ function parseLayeredReference(value) {
|
|
|
15820
16104
|
return { id: trimmed };
|
|
15821
16105
|
}
|
|
15822
16106
|
async function collectSessionSources(opts, tool, store) {
|
|
15823
|
-
const
|
|
15824
|
-
const sources = (opts.source ?? []).map((value, index) => parseSessionSource(value, index
|
|
16107
|
+
const replacements = sessionSourceReplacements(opts.replaceSource ?? []);
|
|
16108
|
+
const sources = (opts.source ?? []).map((value, index) => parseSessionSource(value, index));
|
|
15825
16109
|
for (const value of opts.config ?? []) {
|
|
15826
16110
|
const { layer, id } = parseLayeredReference(value);
|
|
15827
16111
|
sources.push(sourceFromConfig(await store.getConfig(id), sources.length, layer));
|
|
@@ -15833,7 +16117,16 @@ async function collectSessionSources(opts, tool, store) {
|
|
|
15833
16117
|
const parsed = JSON.parse(readFileSync12(path, "utf-8"));
|
|
15834
16118
|
sources.push(...sourcesFromIdentityExport(parsed, { path, tool, orderOffset: sources.length }));
|
|
15835
16119
|
}
|
|
15836
|
-
return sources.map((source) =>
|
|
16120
|
+
return sources.map((source) => {
|
|
16121
|
+
const replacement = replacements.get(source.id);
|
|
16122
|
+
if (!replacement)
|
|
16123
|
+
return source;
|
|
16124
|
+
return {
|
|
16125
|
+
...source,
|
|
16126
|
+
merge: "replace",
|
|
16127
|
+
replacementScope: replacement.replacementScope
|
|
16128
|
+
};
|
|
16129
|
+
});
|
|
15837
16130
|
}
|
|
15838
16131
|
async function checkGlobalSourceCoverage(plan, store) {
|
|
15839
16132
|
const registryConfigs = await store.listConfigs({});
|
|
@@ -16576,7 +16869,7 @@ projectContextCmd.command("apply").description("Atomically write project context
|
|
|
16576
16869
|
}
|
|
16577
16870
|
});
|
|
16578
16871
|
var sessionCmd = program.command("session").description("Plan and apply session-scoped agent instruction files");
|
|
16579
|
-
sessionCmd.command("plan").description("Produce a dry-run render plan for profile-scoped instruction injection").requiredOption("--tool <tool>", `target tool (${SESSION_RENDER_TOOLS.join("|")})`).requiredOption("--profile <profile>", "account/profile name that owns the rendered instruction home").option("--target-home <path>", "override generated profile-scoped target home").option("--project-root <path>", "repository root for project-scoped adapters such as Cursor").option("--session-id <id>", "session id to include in the manifest").option("--source <layer:id=path>", `instruction source file; layers: ${SESSION_SOURCE_LAYER_HELP}`, collectOption, []).option("--config <layer:id-or-slug>", "stored config source by id/slug; repeatable; layer aliases match --source", collectOption, []).option("--identity-export <path>", "OpenIdentities configs instruction export JSON; repeatable", collectOption, []).option("--replace-source <id>", "source id that replaces earlier layers
|
|
16872
|
+
sessionCmd.command("plan").description("Produce a dry-run render plan for profile-scoped instruction injection").requiredOption("--tool <tool>", `target tool (${SESSION_RENDER_TOOLS.join("|")})`).requiredOption("--profile <profile>", "account/profile name that owns the rendered instruction home").option("--target-home <path>", "override generated profile-scoped target home").option("--project-root <path>", "repository root for project-scoped adapters such as Cursor").option("--session-id <id>", "session id to include in the manifest").option("--source <layer:id=path>", `instruction source file; layers: ${SESSION_SOURCE_LAYER_HELP}`, collectOption, []).option("--config <layer:id-or-slug>", "stored config source by id/slug; repeatable; layer aliases match --source", collectOption, []).option("--identity-export <path>", "OpenIdentities configs instruction export JSON; repeatable", collectOption, []).option("--replace-source <replacer-id>[=<target-source-id>]", "source id that broadly replaces earlier layers, or targets one earlier source", collectOption, []).option("--codewith-native-imports", "select the gated Codewith native @ import adapter").option("--allow-empty-sources", "allow an explicit empty render plan").option("--check-global-coverage", "warn (non-fatal) when a registered, non-retired global-* source is absent from this render's --config list; expected is read fresh from the registry, independent of this plan (todos 102d6d0a)").option("--json", "output dry-run JSON").action(async (opts) => {
|
|
16580
16873
|
try {
|
|
16581
16874
|
const tool = opts.tool;
|
|
16582
16875
|
if (!SESSION_RENDER_TOOLS.includes(tool)) {
|
|
@@ -16632,7 +16925,7 @@ sessionCmd.command("plan").description("Produce a dry-run render plan for profil
|
|
|
16632
16925
|
process.exit(1);
|
|
16633
16926
|
}
|
|
16634
16927
|
});
|
|
16635
|
-
sessionCmd.command("apply").description("Write a session render plan to its managed target home or explicit project root").requiredOption("--tool <tool>", `target tool (${SESSION_RENDER_TOOLS.join("|")})`).requiredOption("--profile <profile>", "account/profile name that owns the rendered instruction home").option("--target-home <path>", "override generated profile-scoped target home").option("--project-root <path>", "repository root for project-scoped adapters such as Cursor").option("--session-id <id>", "session id to include in the manifest").option("--source <layer:id=path>", `instruction source file; layers: ${SESSION_SOURCE_LAYER_HELP}`, collectOption, []).option("--config <layer:id-or-slug>", "stored config source by id/slug; repeatable; layer aliases match --source", collectOption, []).option("--identity-export <path>", "OpenIdentities configs instruction export JSON; repeatable", collectOption, []).option("--replace-source <id>", "source id that replaces earlier layers
|
|
16928
|
+
sessionCmd.command("apply").description("Write a session render plan to its managed target home or explicit project root").requiredOption("--tool <tool>", `target tool (${SESSION_RENDER_TOOLS.join("|")})`).requiredOption("--profile <profile>", "account/profile name that owns the rendered instruction home").option("--target-home <path>", "override generated profile-scoped target home").option("--project-root <path>", "repository root for project-scoped adapters such as Cursor").option("--session-id <id>", "session id to include in the manifest").option("--source <layer:id=path>", `instruction source file; layers: ${SESSION_SOURCE_LAYER_HELP}`, collectOption, []).option("--config <layer:id-or-slug>", "stored config source by id/slug; repeatable; layer aliases match --source", collectOption, []).option("--identity-export <path>", "OpenIdentities configs instruction export JSON; repeatable", collectOption, []).option("--replace-source <replacer-id>[=<target-source-id>]", "source id that broadly replaces earlier layers, or targets one earlier source", collectOption, []).option("--codewith-native-imports", "select the gated Codewith native @ import adapter").option("--allow-empty-sources", "allow an explicit empty render").option("--check-global-coverage", "warn (non-fatal) when a registered, non-retired global-* source is absent from this render's --config list; expected is read fresh from the registry, independent of this plan (todos 102d6d0a)").option("--dry-run", "preview writes and conflicts without writing").option("--force", "overwrite existing unmanaged files").option("--json", "output apply JSON").action(async (opts) => {
|
|
16636
16929
|
try {
|
|
16637
16930
|
const tool = opts.tool;
|
|
16638
16931
|
if (!SESSION_RENDER_TOOLS.includes(tool)) {
|
|
@@ -17325,7 +17618,7 @@ program.command("watch").description("Watch known config files for changes and a
|
|
|
17325
17618
|
setInterval(tick, interval);
|
|
17326
17619
|
await new Promise(() => {});
|
|
17327
17620
|
});
|
|
17328
|
-
program.command("report").description("Summary of stored configs, drift, and ecosystem health").option("--json", "output as JSON").option("--markdown", "output as markdown").action(async () => {
|
|
17621
|
+
program.command("report").description("Summary of stored configs, drift, and ecosystem health").option("--json", "output as JSON").option("--markdown", "output as markdown").action(async (opts) => {
|
|
17329
17622
|
const store = resolveConfigStore();
|
|
17330
17623
|
const stats = await store.getConfigStats();
|
|
17331
17624
|
const allConfigs = await store.listConfigs();
|
|
@@ -17351,6 +17644,32 @@ program.command("report").description("Summary of stored configs, drift, and eco
|
|
|
17351
17644
|
for (const c of allConfigs)
|
|
17352
17645
|
byAgent[c.agent] = (byAgent[c.agent] || 0) + 1;
|
|
17353
17646
|
const projectConfigs = allConfigs.filter((c) => c.target_path && !c.target_path.startsWith("~/."));
|
|
17647
|
+
if (opts.json) {
|
|
17648
|
+
printJson({
|
|
17649
|
+
schema_version: 1,
|
|
17650
|
+
configs: {
|
|
17651
|
+
total: allConfigs.length,
|
|
17652
|
+
files: fileConfigs.length,
|
|
17653
|
+
references: refConfigs.length,
|
|
17654
|
+
templates: templates.length,
|
|
17655
|
+
project: projectConfigs.length
|
|
17656
|
+
},
|
|
17657
|
+
profiles: {
|
|
17658
|
+
total: profiles.length
|
|
17659
|
+
},
|
|
17660
|
+
drift: {
|
|
17661
|
+
drifted,
|
|
17662
|
+
missing
|
|
17663
|
+
},
|
|
17664
|
+
secrets: {
|
|
17665
|
+
findings: 0,
|
|
17666
|
+
policy: "redacted_on_ingest"
|
|
17667
|
+
},
|
|
17668
|
+
by_agent: byAgent,
|
|
17669
|
+
by_category: Object.fromEntries(Object.entries(stats).filter(([key]) => key !== "total").map(([key, value]) => [key, Number(value)]))
|
|
17670
|
+
});
|
|
17671
|
+
return;
|
|
17672
|
+
}
|
|
17354
17673
|
console.log(chalk.bold(`configs report
|
|
17355
17674
|
`));
|
|
17356
17675
|
console.log(` Total: ${allConfigs.length} configs (${fileConfigs.length} files, ${refConfigs.length} references)`);
|