@hasna/instructions 0.4.8 → 0.4.10
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 +35 -0
- package/dist/cli/index.js +513 -232
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +481 -200
- package/dist/lib/apply.d.ts +7 -0
- package/dist/lib/apply.d.ts.map +1 -1
- package/dist/lib/global-agent-rules-standard.d.ts +106 -0
- package/dist/lib/global-agent-rules-standard.d.ts.map +1 -1
- package/dist/lib/global-agent-rules-stored-content.test.d.ts +2 -0
- package/dist/lib/global-agent-rules-stored-content.test.d.ts.map +1 -0
- package/dist/lib/project-context-prepared-file.test.d.ts +2 -0
- package/dist/lib/project-context-prepared-file.test.d.ts.map +1 -0
- package/dist/lib/project-context.d.ts +10 -0
- package/dist/lib/project-context.d.ts.map +1 -1
- package/dist/lib/session-render-contract.d.ts +10 -0
- package/dist/lib/session-render-contract.d.ts.map +1 -1
- package/dist/lib/session-render-ownership.d.ts +23 -0
- package/dist/lib/session-render-ownership.d.ts.map +1 -0
- package/dist/lib/session-render-ownership.test.d.ts +2 -0
- package/dist/lib/session-render-ownership.test.d.ts.map +1 -0
- package/dist/lib/session-render.d.ts +24 -1
- package/dist/lib/session-render.d.ts.map +1 -1
- package/dist/lib/test-temp-root.d.ts +7 -0
- package/dist/lib/test-temp-root.d.ts.map +1 -0
- package/dist/mcp/index.js +214 -39
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1100,11 +1100,11 @@ function resolveConfigStore(env = process.env) {
|
|
|
1100
1100
|
return cloud ? new CloudConfigStore(cloud) : new LocalConfigStore;
|
|
1101
1101
|
}
|
|
1102
1102
|
// src/status.ts
|
|
1103
|
-
import { existsSync as
|
|
1103
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
|
|
1104
1104
|
|
|
1105
1105
|
// src/lib/apply.ts
|
|
1106
|
-
import { existsSync as
|
|
1107
|
-
import { basename as basename4, dirname as
|
|
1106
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync4, realpathSync as realpathSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1107
|
+
import { basename as basename4, dirname as dirname4, join as join6, resolve as resolve3 } from "path";
|
|
1108
1108
|
import { homedir as homedir3 } from "os";
|
|
1109
1109
|
|
|
1110
1110
|
// src/lib/config-agents.ts
|
|
@@ -1127,18 +1127,23 @@ function retiredOrUnsupportedAgentReason(agent) {
|
|
|
1127
1127
|
}
|
|
1128
1128
|
|
|
1129
1129
|
// src/lib/session-render.ts
|
|
1130
|
-
import { createHash as
|
|
1130
|
+
import { createHash as createHash3 } from "crypto";
|
|
1131
1131
|
import { existsSync as existsSync4, readFileSync as readFileSync2, realpathSync, statSync as statSync2 } from "fs";
|
|
1132
1132
|
import { homedir as homedir2 } from "os";
|
|
1133
1133
|
import { basename as basename3, dirname as dirname2, extname as extname2, isAbsolute as isAbsolute2, join as join4, parse as parse2, posix, relative as relative2, resolve as resolve2 } from "path";
|
|
1134
1134
|
|
|
1135
1135
|
// src/lib/global-agent-rules-standard.ts
|
|
1136
|
+
import { createHash } from "crypto";
|
|
1136
1137
|
var GLOBAL_AGENT_RULES_STANDARD_SLUG = "global-agent-rules-standard";
|
|
1137
1138
|
var AGENT_OPERATING_RULES_SOURCE_SET_ID = "hasna-global-agent-rules-standard";
|
|
1138
1139
|
var AGENT_OPERATING_RULES_SOURCE_ID = "hasna-agent-operating-rules";
|
|
1140
|
+
var AGENT_OPERATING_RULES_ROLE = "agent-operating-rules";
|
|
1139
1141
|
var AGENT_OPERATING_RULES_VERSION = "1.1.6";
|
|
1140
1142
|
var AGENT_OPERATING_RULES_SOURCE_SET_VERSION = "2026-07-23";
|
|
1141
1143
|
var AGENT_OPERATING_RULES_SENTINEL = "<!-- hasna:agent-operating-rules v=1.1.6 -->";
|
|
1144
|
+
var AGENT_OPERATING_RULES_SEMANTIC_POLICY_KEY = "hasna:agent-operating-rules";
|
|
1145
|
+
var AGENT_OPERATING_RULES_SENTINEL_PATTERN = /<!--\s*hasna:agent-operating-rules\s+v=([0-9]+\.[0-9]+\.[0-9]+)\s*-->/i;
|
|
1146
|
+
var AGENT_OPERATING_RULES_HEADING_PATTERN = /^#\s*Hasna Agent Operating Rules\s+\u2014\s+v[0-9]+\.[0-9]+\.[0-9]+\s+\(([0-9]{4}-[0-9]{2}-[0-9]{2})\)/;
|
|
1142
1147
|
var AGENT_OPERATING_RULES_PAYLOAD_SHA256 = "8b236086b82e94490516e0b00dffa03fb5f6841b68d95f80fc3e3c8fb7087420";
|
|
1143
1148
|
var AGENT_OPERATING_RULES_UPSTREAM_FILE_SHA256 = "b8e89cdb49e207e5b497ac51384d67022b94fe5645cc9273db60384eb2c2fb32";
|
|
1144
1149
|
var SCOPED_OPERATIONAL_CONTROL_POLICY_REFERENCE = "hasna-agent-operating-rules/scoped-operational-control/v1";
|
|
@@ -1170,7 +1175,7 @@ var AGENT_OPERATING_RULES_PROVENANCE = {
|
|
|
1170
1175
|
};
|
|
1171
1176
|
var AGENT_OPERATING_RULES_METADATA = {
|
|
1172
1177
|
sourceSet: AGENT_OPERATING_RULES_SOURCE_SET_ID,
|
|
1173
|
-
role:
|
|
1178
|
+
role: AGENT_OPERATING_RULES_ROLE,
|
|
1174
1179
|
rulesVersion: AGENT_OPERATING_RULES_VERSION,
|
|
1175
1180
|
sourceSetVersion: AGENT_OPERATING_RULES_SOURCE_SET_VERSION,
|
|
1176
1181
|
plan: GLOBAL_AGENT_RULES_STANDARD_SLUG,
|
|
@@ -1179,7 +1184,7 @@ var AGENT_OPERATING_RULES_METADATA = {
|
|
|
1179
1184
|
upstreamFileSha256: AGENT_OPERATING_RULES_UPSTREAM_FILE_SHA256,
|
|
1180
1185
|
upstreamExportId: AGENT_OPERATING_RULES_SOURCE_SET_ID,
|
|
1181
1186
|
upstreamSourceId: AGENT_OPERATING_RULES_SOURCE_ID,
|
|
1182
|
-
sentinel:
|
|
1187
|
+
sentinel: AGENT_OPERATING_RULES_SEMANTIC_POLICY_KEY,
|
|
1183
1188
|
policyReferences: {
|
|
1184
1189
|
incidentRecovery: SCOPED_OPERATIONAL_CONTROL_POLICY_REFERENCE
|
|
1185
1190
|
}
|
|
@@ -1219,37 +1224,120 @@ var GLOBAL_AGENT_RULES_STANDARD_CONTENT = [
|
|
|
1219
1224
|
].join(`
|
|
1220
1225
|
`) + `
|
|
1221
1226
|
`;
|
|
1222
|
-
|
|
1223
|
-
|
|
1227
|
+
function parseAgentOperatingRulesVersion(content) {
|
|
1228
|
+
return content ? AGENT_OPERATING_RULES_SENTINEL_PATTERN.exec(content)?.[1] ?? null : null;
|
|
1229
|
+
}
|
|
1230
|
+
function compareAgentOperatingRulesVersions(left, right) {
|
|
1231
|
+
const leftParts = left.split(".").map(Number);
|
|
1232
|
+
const rightParts = right.split(".").map(Number);
|
|
1233
|
+
for (let i = 0;i < 3; i++) {
|
|
1234
|
+
const diff = (leftParts[i] ?? 0) - (rightParts[i] ?? 0);
|
|
1235
|
+
if (diff !== 0)
|
|
1236
|
+
return diff;
|
|
1237
|
+
}
|
|
1238
|
+
return 0;
|
|
1239
|
+
}
|
|
1240
|
+
function payloadDate(content) {
|
|
1241
|
+
const canonical = new RegExp(AGENT_OPERATING_RULES_HEADING_PATTERN.source, "m").exec(content)?.[1];
|
|
1242
|
+
if (canonical)
|
|
1243
|
+
return canonical;
|
|
1244
|
+
const heading = /^#[^\S\n].*$/m.exec(content)?.[0];
|
|
1245
|
+
return heading ? /\b([0-9]{4}-[0-9]{2}-[0-9]{2})\b/.exec(heading)?.[1] ?? null : null;
|
|
1246
|
+
}
|
|
1247
|
+
function sha256(content) {
|
|
1248
|
+
return createHash("sha256").update(content).digest("hex");
|
|
1249
|
+
}
|
|
1250
|
+
function resolveAgentOperatingRulesPayload(storedContent) {
|
|
1251
|
+
const stored = storedContent ?? "";
|
|
1252
|
+
const storedVersion = parseAgentOperatingRulesVersion(stored);
|
|
1253
|
+
const baselineOrder = storedVersion === null ? null : compareAgentOperatingRulesVersions(storedVersion, AGENT_OPERATING_RULES_VERSION);
|
|
1254
|
+
const storedIsCurrent = baselineOrder !== null && (baselineOrder > 0 || baselineOrder === 0 && sha256(stored) === AGENT_OPERATING_RULES_PAYLOAD_SHA256);
|
|
1255
|
+
const content = storedIsCurrent ? stored : GLOBAL_AGENT_RULES_STANDARD_CONTENT;
|
|
1256
|
+
const origin = storedIsCurrent ? "stored-config" : "embedded-baseline";
|
|
1257
|
+
const matchesEmbeddedBaseline = content === GLOBAL_AGENT_RULES_STANDARD_CONTENT;
|
|
1258
|
+
const integrity = matchesEmbeddedBaseline ? "pinned-digest" : "unverified-self-declared";
|
|
1259
|
+
const version = storedIsCurrent ? storedVersion : AGENT_OPERATING_RULES_VERSION;
|
|
1260
|
+
const payloadSha256 = matchesEmbeddedBaseline ? AGENT_OPERATING_RULES_PAYLOAD_SHA256 : sha256(content);
|
|
1261
|
+
const sourceSetVersion = matchesEmbeddedBaseline ? AGENT_OPERATING_RULES_SOURCE_SET_VERSION : payloadDate(content);
|
|
1262
|
+
const upstreamPin = matchesEmbeddedBaseline ? {
|
|
1263
|
+
upstreamRepository: AGENT_OPERATING_RULES_UPSTREAM.repository,
|
|
1264
|
+
upstreamCommit: AGENT_OPERATING_RULES_UPSTREAM.commit,
|
|
1265
|
+
upstreamPath: AGENT_OPERATING_RULES_UPSTREAM.path,
|
|
1266
|
+
upstreamFileSha256: AGENT_OPERATING_RULES_UPSTREAM_FILE_SHA256
|
|
1267
|
+
} : {};
|
|
1268
|
+
const policyReference = content.includes(SCOPED_OPERATIONAL_CONTROL_POLICY_REFERENCE) ? { policyReference: SCOPED_OPERATIONAL_CONTROL_POLICY_REFERENCE } : {};
|
|
1269
|
+
return {
|
|
1270
|
+
content,
|
|
1271
|
+
version,
|
|
1272
|
+
origin,
|
|
1273
|
+
matchesEmbeddedBaseline,
|
|
1274
|
+
integrity,
|
|
1275
|
+
provenance: {
|
|
1276
|
+
source: AGENT_OPERATING_RULES_PROVENANCE.source,
|
|
1277
|
+
payloadOrigin: origin,
|
|
1278
|
+
payloadIntegrity: integrity,
|
|
1279
|
+
...upstreamPin,
|
|
1280
|
+
upstreamExportId: AGENT_OPERATING_RULES_SOURCE_SET_ID,
|
|
1281
|
+
upstreamSourceId: AGENT_OPERATING_RULES_SOURCE_ID,
|
|
1282
|
+
selectedPayloadSha256: payloadSha256,
|
|
1283
|
+
rulesVersion: version,
|
|
1284
|
+
sourceSetVersion,
|
|
1285
|
+
...policyReference
|
|
1286
|
+
},
|
|
1287
|
+
metadata: {
|
|
1288
|
+
sourceSet: AGENT_OPERATING_RULES_SOURCE_SET_ID,
|
|
1289
|
+
role: AGENT_OPERATING_RULES_METADATA.role,
|
|
1290
|
+
payloadOrigin: origin,
|
|
1291
|
+
payloadIntegrity: integrity,
|
|
1292
|
+
rulesVersion: version,
|
|
1293
|
+
sourceSetVersion,
|
|
1294
|
+
plan: GLOBAL_AGENT_RULES_STANDARD_SLUG,
|
|
1295
|
+
contentSha256: payloadSha256,
|
|
1296
|
+
selectedPayloadSha256: payloadSha256,
|
|
1297
|
+
...matchesEmbeddedBaseline ? { upstreamFileSha256: AGENT_OPERATING_RULES_UPSTREAM_FILE_SHA256 } : {},
|
|
1298
|
+
upstreamExportId: AGENT_OPERATING_RULES_SOURCE_SET_ID,
|
|
1299
|
+
upstreamSourceId: AGENT_OPERATING_RULES_SOURCE_ID,
|
|
1300
|
+
sentinel: AGENT_OPERATING_RULES_METADATA.sentinel,
|
|
1301
|
+
...policyReference.policyReference ? { policyReferences: { incidentRecovery: SCOPED_OPERATIONAL_CONTROL_POLICY_REFERENCE } } : {}
|
|
1302
|
+
}
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
function standardConfigInput(payload) {
|
|
1306
|
+
return {
|
|
1224
1307
|
name: "Global Agent Rules Standard",
|
|
1225
1308
|
category: "rules",
|
|
1226
1309
|
agent: "global",
|
|
1227
1310
|
format: "markdown",
|
|
1228
|
-
content:
|
|
1311
|
+
content: payload.content,
|
|
1229
1312
|
kind: "reference",
|
|
1230
|
-
description: `Managed Hasna agent operating rules v${
|
|
1313
|
+
description: payload.matchesEmbeddedBaseline ? `Managed Hasna agent operating rules v${payload.version}; accepted source ${AGENT_OPERATING_RULES_UPSTREAM.repository}@${AGENT_OPERATING_RULES_UPSTREAM.commit}:${AGENT_OPERATING_RULES_UPSTREAM.path}` : `Managed Hasna agent operating rules v${payload.version}; stored payload sha256 ${payload.metadata["contentSha256"]}`,
|
|
1231
1314
|
tags: [
|
|
1232
1315
|
"global-agent-rules",
|
|
1233
1316
|
"system-prompt",
|
|
1234
1317
|
"coding-agent-rules",
|
|
1235
1318
|
"agent-operating-rules",
|
|
1236
|
-
`rules-version:${
|
|
1237
|
-
`source-commit:${AGENT_OPERATING_RULES_UPSTREAM.commit}`
|
|
1319
|
+
`rules-version:${payload.version}`,
|
|
1320
|
+
...payload.matchesEmbeddedBaseline ? [`source-commit:${AGENT_OPERATING_RULES_UPSTREAM.commit}`] : []
|
|
1238
1321
|
]
|
|
1239
1322
|
};
|
|
1323
|
+
}
|
|
1324
|
+
async function ensureGlobalAgentRulesStandardConfig(store = resolveConfigStore()) {
|
|
1325
|
+
let existing;
|
|
1240
1326
|
try {
|
|
1241
|
-
|
|
1242
|
-
if (existing.content !== input.content || existing.description !== input.description || existing.category !== input.category || existing.agent !== input.agent || existing.format !== input.format || existing.kind !== input.kind || JSON.stringify(existing.tags) !== JSON.stringify(input.tags)) {
|
|
1243
|
-
return await store.updateConfig(existing.id, input);
|
|
1244
|
-
}
|
|
1245
|
-
return existing;
|
|
1327
|
+
existing = await store.getConfig(GLOBAL_AGENT_RULES_STANDARD_SLUG);
|
|
1246
1328
|
} catch {
|
|
1247
|
-
return await store.createConfig(
|
|
1329
|
+
return await store.createConfig(standardConfigInput(resolveAgentOperatingRulesPayload(null)));
|
|
1330
|
+
}
|
|
1331
|
+
const payload = resolveAgentOperatingRulesPayload(existing.content);
|
|
1332
|
+
const input = standardConfigInput(payload);
|
|
1333
|
+
if (existing.content !== input.content || existing.description !== input.description || existing.category !== input.category || existing.agent !== input.agent || existing.format !== input.format || existing.kind !== input.kind || JSON.stringify(existing.tags) !== JSON.stringify(input.tags)) {
|
|
1334
|
+
return await store.updateConfig(existing.id, input);
|
|
1248
1335
|
}
|
|
1336
|
+
return existing;
|
|
1249
1337
|
}
|
|
1250
1338
|
|
|
1251
1339
|
// src/lib/project-context.ts
|
|
1252
|
-
import { createHash, randomUUID as randomUUID3 } from "crypto";
|
|
1340
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
1253
1341
|
import { execFileSync } from "child_process";
|
|
1254
1342
|
import { dlopen, FFIType } from "bun:ffi";
|
|
1255
1343
|
import {
|
|
@@ -5432,6 +5520,10 @@ function hasSecrets(content, format) {
|
|
|
5432
5520
|
var CODEWITH_NATIVE_IMPORTS_ENV = "HASNA_CONFIGS_CODEWITH_NATIVE_IMPORTS";
|
|
5433
5521
|
var SESSION_RENDER_MANAGED_MARKER = "Managed by @hasna/configs session render";
|
|
5434
5522
|
var SESSION_RENDER_SCHEMA = "hasna.configs.session-render/v1";
|
|
5523
|
+
var SESSION_RENDER_MANAGED_NAMESPACE = ".hasna";
|
|
5524
|
+
var SESSION_RENDER_INSTRUCTIONS_MANAGED_DIR = `${SESSION_RENDER_MANAGED_NAMESPACE}/instructions`;
|
|
5525
|
+
var SESSION_RENDER_MANIFEST_RELATIVE_PATH = `${SESSION_RENDER_MANAGED_NAMESPACE}/session-render-manifest.json`;
|
|
5526
|
+
var SESSION_RENDER_SNAPSHOT_RELATIVE_DIR = `${SESSION_RENDER_MANAGED_NAMESPACE}/session-render-snapshots`;
|
|
5435
5527
|
var SESSION_INSTRUCTION_LAYERS = [
|
|
5436
5528
|
"global",
|
|
5437
5529
|
"tool",
|
|
@@ -5608,7 +5700,7 @@ class ProjectContextHashRace extends Error {
|
|
|
5608
5700
|
}
|
|
5609
5701
|
function computeProjectContextSourceHash(value) {
|
|
5610
5702
|
const normalized = removeHashForFingerprint(value);
|
|
5611
|
-
return `sha256:${
|
|
5703
|
+
return `sha256:${sha2562(stableStringify(normalized))}`;
|
|
5612
5704
|
}
|
|
5613
5705
|
function parseProjectContextBundle(input) {
|
|
5614
5706
|
let encoded;
|
|
@@ -5778,7 +5870,7 @@ function composeProjectContextSessionRender(input) {
|
|
|
5778
5870
|
const files = input.files.map((file) => file === index ? {
|
|
5779
5871
|
...file,
|
|
5780
5872
|
content,
|
|
5781
|
-
sha256:
|
|
5873
|
+
sha256: sha2562(content),
|
|
5782
5874
|
sourceIds: [...new Set([...file.sourceIds, "project-context-bundle"])]
|
|
5783
5875
|
} : file);
|
|
5784
5876
|
if (observedHashes.some((observed) => currentFileHash(observed.path, workspaceRoot) !== observed.sha256)) {
|
|
@@ -5979,7 +6071,7 @@ function assertRenderedOutputsStable(plan, cacheContent, sessionOutput) {
|
|
|
5979
6071
|
sessionOutput
|
|
5980
6072
|
];
|
|
5981
6073
|
for (const output of outputs) {
|
|
5982
|
-
if (currentFileHash(output.path, plan.workspace_root) !==
|
|
6074
|
+
if (currentFileHash(output.path, plan.workspace_root) !== sha2562(output.content)) {
|
|
5983
6075
|
throw new ProjectContextHashRace(`managed path changed before manifest commit: ${relativePosix(plan.workspace_root, output.path)}`);
|
|
5984
6076
|
}
|
|
5985
6077
|
}
|
|
@@ -6199,7 +6291,7 @@ function findLegacyCodewithWorkspaceSection(workspaceRoot, runtime, content, bun
|
|
|
6199
6291
|
return null;
|
|
6200
6292
|
const files = Array.isArray(manifest["files"]) ? manifest["files"] : [];
|
|
6201
6293
|
const codewith = files.find((file) => isRecord(file) && file["relativePath"] === "CODEWITH.md");
|
|
6202
|
-
if (!isRecord(codewith) || codewith["sha256"] !==
|
|
6294
|
+
if (!isRecord(codewith) || codewith["sha256"] !== sha2562(content)) {
|
|
6203
6295
|
throw new ProjectContextError("MANAGED_BLOCK_CONFLICT", "legacy /dev/fd session manifest does not match CODEWITH.md");
|
|
6204
6296
|
}
|
|
6205
6297
|
const section = /^## Workspace\r?\n/gm.exec(content);
|
|
@@ -6269,14 +6361,14 @@ function buildManifest(plan, now2) {
|
|
|
6269
6361
|
path: plan.fragment_path,
|
|
6270
6362
|
relativePath: PROJECT_CONTEXT_FRAGMENT_PATH,
|
|
6271
6363
|
role: "fragment",
|
|
6272
|
-
sha256:
|
|
6364
|
+
sha256: sha2562(plan.fragment),
|
|
6273
6365
|
sourceIds: ["project-context-bundle"]
|
|
6274
6366
|
},
|
|
6275
6367
|
{
|
|
6276
6368
|
path: plan.target_path,
|
|
6277
6369
|
relativePath: plan.target_relative_path,
|
|
6278
6370
|
role: "index",
|
|
6279
|
-
sha256:
|
|
6371
|
+
sha256: sha2562(plan.target_content),
|
|
6280
6372
|
sourceIds: ["project-context-bundle"]
|
|
6281
6373
|
}
|
|
6282
6374
|
];
|
|
@@ -6353,7 +6445,7 @@ function buildSessionCompatibilityManifest(plan, now2) {
|
|
|
6353
6445
|
path: plan.target_path,
|
|
6354
6446
|
relativePath: targetRelativePath,
|
|
6355
6447
|
role: "index",
|
|
6356
|
-
sha256:
|
|
6448
|
+
sha256: sha2562(plan.target_content),
|
|
6357
6449
|
sourceIds: [...new Set([...previousSourceIds ?? [], "project-context-bundle"])]
|
|
6358
6450
|
};
|
|
6359
6451
|
const targetOwner = isRecord(existing["targetOwner"]) ? existing["targetOwner"] : {};
|
|
@@ -6381,7 +6473,7 @@ function buildSessionCompatibilityManifest(plan, now2) {
|
|
|
6381
6473
|
blockers: [],
|
|
6382
6474
|
generatedAt: now2.toISOString(),
|
|
6383
6475
|
env: sanitizeLegacyEnvironment(existing["env"]),
|
|
6384
|
-
sourceHash:
|
|
6476
|
+
sourceHash: sha2562(stableStringify({ previous: typeof existing["sourceHash"] === "string" ? existing["sourceHash"] : null, projectContext: plan.bundle.hash })),
|
|
6385
6477
|
sources,
|
|
6386
6478
|
skippedSources: sanitizeLegacySkippedSources(existing["skippedSources"]),
|
|
6387
6479
|
files: [...files.filter((file) => file["relativePath"] !== targetRelativePath), updatedTarget],
|
|
@@ -6584,7 +6676,7 @@ function projectContextManifestSource(cachePath, runtime, bundle) {
|
|
|
6584
6676
|
nonOverridable: true,
|
|
6585
6677
|
replacementScope: "project-context",
|
|
6586
6678
|
rules: [],
|
|
6587
|
-
renderedPayloadSha256:
|
|
6679
|
+
renderedPayloadSha256: sha2562(JSON.stringify(bundle)),
|
|
6588
6680
|
provenance: {
|
|
6589
6681
|
schema: PROJECT_CONTEXT_SCHEMA,
|
|
6590
6682
|
projectId: bundle.project.id,
|
|
@@ -6706,7 +6798,7 @@ function atomicWriteFile(path, content, workspaceRoot, defaultMode, expectedHash
|
|
|
6706
6798
|
let fd = null;
|
|
6707
6799
|
let preserveTemp = false;
|
|
6708
6800
|
let directoryChanged = false;
|
|
6709
|
-
const desiredHash =
|
|
6801
|
+
const desiredHash = sha2562(content);
|
|
6710
6802
|
try {
|
|
6711
6803
|
fd = anchoredOpenExclusive(directory, tempName, previousMode);
|
|
6712
6804
|
writeFileSync(fd, content, { encoding: "utf8" });
|
|
@@ -6715,7 +6807,7 @@ function atomicWriteFile(path, content, workspaceRoot, defaultMode, expectedHash
|
|
|
6715
6807
|
fd = null;
|
|
6716
6808
|
beforeInstall?.(tempPath);
|
|
6717
6809
|
assertManagedDirectoryStable(dir, workspaceRoot, directory.identity);
|
|
6718
|
-
if (
|
|
6810
|
+
if (anchoredPreparedObservation(directory, tempName, path, "before installation").hash !== desiredHash) {
|
|
6719
6811
|
throw new ProjectContextHashRace(`prepared bytes changed before installation: ${relativePosix(workspaceRoot, path)}`);
|
|
6720
6812
|
}
|
|
6721
6813
|
if (expectedHash === undefined) {
|
|
@@ -6724,8 +6816,8 @@ function atomicWriteFile(path, content, workspaceRoot, defaultMode, expectedHash
|
|
|
6724
6816
|
}
|
|
6725
6817
|
directoryChanged = true;
|
|
6726
6818
|
} else if (expectedHash === null) {
|
|
6727
|
-
const prepared =
|
|
6728
|
-
if (
|
|
6819
|
+
const prepared = anchoredPreparedObservation(directory, tempName, path, "before creation");
|
|
6820
|
+
if (anchoredFileObservation(directory, targetName) !== null) {
|
|
6729
6821
|
throw new ProjectContextHashRace(`managed path appeared before creation: ${relativePosix(workspaceRoot, path)}`);
|
|
6730
6822
|
}
|
|
6731
6823
|
if (!directory.ops.linkat(directory.fd, tempName, directory.fd, targetName)) {
|
|
@@ -6733,7 +6825,8 @@ function atomicWriteFile(path, content, workspaceRoot, defaultMode, expectedHash
|
|
|
6733
6825
|
}
|
|
6734
6826
|
directoryChanged = true;
|
|
6735
6827
|
const installed = anchoredFileObservation(directory, targetName);
|
|
6736
|
-
|
|
6828
|
+
const stagedHash = anchoredFileHash(directory, tempName);
|
|
6829
|
+
if (!installed || installed.dev !== prepared.dev || installed.ino !== prepared.ino || stagedHash !== desiredHash || installed.hash !== desiredHash) {
|
|
6737
6830
|
directory.ops.unlinkat(directory.fd, tempName);
|
|
6738
6831
|
preserveTemp = true;
|
|
6739
6832
|
throw new ProjectContextHashRace(`prepared bytes changed during creation: ${relativePosix(workspaceRoot, path)}`);
|
|
@@ -6750,7 +6843,7 @@ function atomicWriteFile(path, content, workspaceRoot, defaultMode, expectedHash
|
|
|
6750
6843
|
if (anchoredFileHash(directory, targetName) !== expectedHash) {
|
|
6751
6844
|
throw new ProjectContextHashRace(`managed path changed before atomic replacement: ${relativePosix(workspaceRoot, path)}`);
|
|
6752
6845
|
}
|
|
6753
|
-
if (
|
|
6846
|
+
if (anchoredPreparedObservation(directory, tempName, path, "before atomic replacement").hash !== desiredHash) {
|
|
6754
6847
|
throw new ProjectContextHashRace(`prepared bytes changed before atomic replacement: ${relativePosix(workspaceRoot, path)}`);
|
|
6755
6848
|
}
|
|
6756
6849
|
atomicExchangeEntries(directory.fd, tempName, targetName);
|
|
@@ -6816,7 +6909,7 @@ function atomicWriteFile(path, content, workspaceRoot, defaultMode, expectedHash
|
|
|
6816
6909
|
}
|
|
6817
6910
|
}
|
|
6818
6911
|
function atomicWritePortable(path, content, workspaceRoot, defaultMode, expectedHash, beforeInstall, maxObservedBytes, allowReplacement = false) {
|
|
6819
|
-
const desiredHash =
|
|
6912
|
+
const desiredHash = sha2562(content);
|
|
6820
6913
|
const currentHash = portableFileHash(path, workspaceRoot, maxObservedBytes);
|
|
6821
6914
|
if (expectedHash === undefined && currentHash === desiredHash)
|
|
6822
6915
|
return;
|
|
@@ -6847,7 +6940,7 @@ function atomicWritePortable(path, content, workspaceRoot, defaultMode, expected
|
|
|
6847
6940
|
assertManagedDirectoryStable(dir, workspaceRoot, directoryIdentity);
|
|
6848
6941
|
assertNoSymlinkSegments(workspaceRoot, tempPath);
|
|
6849
6942
|
assertNoSymlinkSegments(workspaceRoot, path);
|
|
6850
|
-
if (
|
|
6943
|
+
if (portablePreparedHash(tempPath, path, workspaceRoot, maxObservedBytes, "before portable creation") !== desiredHash || portableFileHash(path, workspaceRoot, maxObservedBytes) !== null) {
|
|
6851
6944
|
throw new ProjectContextHashRace(`managed path changed before portable creation: ${relativePosix(workspaceRoot, path)}`);
|
|
6852
6945
|
}
|
|
6853
6946
|
const prepared = lstatSync(tempPath);
|
|
@@ -6890,7 +6983,7 @@ function atomicWritePortableReplacement(path, content, workspaceRoot, expectedHa
|
|
|
6890
6983
|
const dir = dirname(path);
|
|
6891
6984
|
const directoryIdentity = captureManagedDirectoryIdentity(dir, workspaceRoot);
|
|
6892
6985
|
const tempPath = join3(dir, `.project-context-${randomUUID3()}.tmp`);
|
|
6893
|
-
const desiredHash =
|
|
6986
|
+
const desiredHash = sha2562(content);
|
|
6894
6987
|
let fd = null;
|
|
6895
6988
|
let tempIdentity = null;
|
|
6896
6989
|
try {
|
|
@@ -6905,7 +6998,7 @@ function atomicWritePortableReplacement(path, content, workspaceRoot, expectedHa
|
|
|
6905
6998
|
assertManagedDirectoryStable(dir, workspaceRoot, directoryIdentity);
|
|
6906
6999
|
assertNoSymlinkSegments(workspaceRoot, tempPath);
|
|
6907
7000
|
assertNoSymlinkSegments(workspaceRoot, path);
|
|
6908
|
-
if (portableFileHash(path, workspaceRoot, maxObservedBytes) !== expectedHash ||
|
|
7001
|
+
if (portableFileHash(path, workspaceRoot, maxObservedBytes) !== expectedHash || portablePreparedHash(tempPath, path, workspaceRoot, maxObservedBytes, "before portable replacement") !== desiredHash) {
|
|
6909
7002
|
throw new ProjectContextHashRace(`managed path changed before portable replacement: ${relativePosix(workspaceRoot, path)}`);
|
|
6910
7003
|
}
|
|
6911
7004
|
renameSync(tempPath, path);
|
|
@@ -6929,6 +7022,20 @@ function atomicWritePortableReplacement(path, content, workspaceRoot, expectedHa
|
|
|
6929
7022
|
throw error;
|
|
6930
7023
|
}
|
|
6931
7024
|
}
|
|
7025
|
+
function portablePreparedHash(tempPath, path, workspaceRoot, maxObservedBytes, stage) {
|
|
7026
|
+
const unreadable = (cause) => new ProjectContextError("PROJECT_CONTEXT_PREPARED_FILE_UNREADABLE", `the prepared managed file could not be read back ${stage}: ${relativePosix(workspaceRoot, path)}`, { staged_path: tempPath, stage, ...cause === undefined ? {} : { cause } });
|
|
7027
|
+
let hash;
|
|
7028
|
+
try {
|
|
7029
|
+
hash = portableFileHash(tempPath, workspaceRoot, maxObservedBytes);
|
|
7030
|
+
} catch (error) {
|
|
7031
|
+
if (error instanceof ProjectContextError || error instanceof ProjectContextHashRace)
|
|
7032
|
+
throw error;
|
|
7033
|
+
throw unreadable(error.message);
|
|
7034
|
+
}
|
|
7035
|
+
if (hash === null)
|
|
7036
|
+
throw unreadable();
|
|
7037
|
+
return hash;
|
|
7038
|
+
}
|
|
6932
7039
|
function portableFileHash(path, workspaceRoot, maxObservedBytes) {
|
|
6933
7040
|
if (maxObservedBytes === undefined)
|
|
6934
7041
|
return currentFileHash(path, workspaceRoot);
|
|
@@ -6941,10 +7048,10 @@ function portableFileHash(path, workspaceRoot, maxObservedBytes) {
|
|
|
6941
7048
|
if (maxObservedBytes !== null && stat.size > maxObservedBytes) {
|
|
6942
7049
|
throw new ProjectContextHashRace(`managed output exceeds the safe read limit: ${relativePosix(workspaceRoot, path)}`);
|
|
6943
7050
|
}
|
|
6944
|
-
return
|
|
7051
|
+
return createHash2("sha256").update(readFileSync(path)).digest("hex");
|
|
6945
7052
|
}
|
|
6946
7053
|
function writeProjectContextCoordinatedFile(input) {
|
|
6947
|
-
atomicWriteFile(resolve(input.path), input.content, assertSafeWorkspaceRoot(input.workspace_root), input.default_mode ?? 420, input.expected_hash, undefined, false,
|
|
7054
|
+
atomicWriteFile(resolve(input.path), input.content, assertSafeWorkspaceRoot(input.workspace_root), input.default_mode ?? 420, input.expected_hash, undefined, false, input.test_hooks?.before_install, input.force_portable_file_ops ?? false, input.max_observed_bytes, input.allow_portable_replacement ?? false);
|
|
6948
7055
|
}
|
|
6949
7056
|
function removeProjectContextCoordinatedFile(input) {
|
|
6950
7057
|
const workspaceRoot = assertSafeWorkspaceRoot(input.workspace_root);
|
|
@@ -7089,15 +7196,43 @@ function openAnchoredDirectory(path, workspaceRoot, providedOps, maxObservedByte
|
|
|
7089
7196
|
}
|
|
7090
7197
|
}
|
|
7091
7198
|
function anchoredOpenExclusive(directory, name, mode) {
|
|
7092
|
-
const
|
|
7093
|
-
|
|
7199
|
+
const requestedMode = mode & 4095;
|
|
7200
|
+
let fd;
|
|
7201
|
+
try {
|
|
7202
|
+
fd = openSync(join3(directory.path, name), constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, requestedMode);
|
|
7203
|
+
} catch {
|
|
7094
7204
|
throw new ProjectContextHashRace(`could not create prepared managed file in ${relativePosix(directory.workspaceRoot, directory.path)}`);
|
|
7095
|
-
|
|
7096
|
-
|
|
7205
|
+
}
|
|
7206
|
+
try {
|
|
7207
|
+
const opened = fstatSync(fd);
|
|
7208
|
+
if (!opened.isFile()) {
|
|
7209
|
+
throw new ProjectContextHashRace("prepared managed output is not a regular file");
|
|
7210
|
+
}
|
|
7211
|
+
if (!isPreparedManagedFileModeUsable(requestedMode, opened.mode)) {
|
|
7212
|
+
throw new ProjectContextError("PROJECT_CONTEXT_PREPARED_FILE_MODE_REJECTED", `the platform created the prepared managed file with an unusable mode in ${relativePosix(directory.workspaceRoot, directory.path)}`, { requested_mode: modeLiteral(requestedMode), observed_mode: modeLiteral(opened.mode) });
|
|
7213
|
+
}
|
|
7214
|
+
assertManagedDirectoryStable(directory.path, directory.workspaceRoot, directory.identity);
|
|
7215
|
+
const anchored = anchoredFileObservation(directory, name);
|
|
7216
|
+
if (!anchored || anchored.dev !== opened.dev || anchored.ino !== opened.ino) {
|
|
7217
|
+
throw new ProjectContextHashRace(`prepared managed file is not the one anchored in ${relativePosix(directory.workspaceRoot, directory.path)}`);
|
|
7218
|
+
}
|
|
7219
|
+
return fd;
|
|
7220
|
+
} catch (error) {
|
|
7097
7221
|
closeSync(fd);
|
|
7098
|
-
throw
|
|
7222
|
+
throw error;
|
|
7099
7223
|
}
|
|
7100
|
-
|
|
7224
|
+
}
|
|
7225
|
+
function modeLiteral(mode) {
|
|
7226
|
+
return `0o${(mode & 4095).toString(8).padStart(3, "0")}`;
|
|
7227
|
+
}
|
|
7228
|
+
function isPreparedManagedFileModeUsable(requestedMode, observedMode) {
|
|
7229
|
+
const requested = requestedMode & 4095;
|
|
7230
|
+
const observed = observedMode & 4095;
|
|
7231
|
+
if ((observed & ~requested) !== 0)
|
|
7232
|
+
return false;
|
|
7233
|
+
if ((requested & 256) !== 0 && (observed & 256) === 0)
|
|
7234
|
+
return false;
|
|
7235
|
+
return true;
|
|
7101
7236
|
}
|
|
7102
7237
|
function anchoredFileObservation(directory, name) {
|
|
7103
7238
|
const fd = directory.ops.openat(directory.fd, name, constants.O_RDONLY | constants.O_NOFOLLOW, 0);
|
|
@@ -7115,7 +7250,7 @@ function anchoredFileObservation(directory, name) {
|
|
|
7115
7250
|
return {
|
|
7116
7251
|
dev: stat.dev,
|
|
7117
7252
|
ino: stat.ino,
|
|
7118
|
-
hash:
|
|
7253
|
+
hash: createHash2("sha256").update(readFileSync(fd)).digest("hex"),
|
|
7119
7254
|
mode: stat.mode & 511
|
|
7120
7255
|
};
|
|
7121
7256
|
} finally {
|
|
@@ -7125,6 +7260,13 @@ function anchoredFileObservation(directory, name) {
|
|
|
7125
7260
|
function anchoredFileHash(directory, name) {
|
|
7126
7261
|
return anchoredFileObservation(directory, name)?.hash ?? null;
|
|
7127
7262
|
}
|
|
7263
|
+
function anchoredPreparedObservation(directory, name, path, stage) {
|
|
7264
|
+
const observed = anchoredFileObservation(directory, name);
|
|
7265
|
+
if (!observed) {
|
|
7266
|
+
throw new ProjectContextError("PROJECT_CONTEXT_PREPARED_FILE_UNREADABLE", `the prepared managed file could not be read back ${stage}: ${relativePosix(directory.workspaceRoot, path)}`, { staged_name: name, stage });
|
|
7267
|
+
}
|
|
7268
|
+
return observed;
|
|
7269
|
+
}
|
|
7128
7270
|
function captureManagedDirectoryIdentity(path, workspaceRoot) {
|
|
7129
7271
|
assertNoSymlinkSegments(workspaceRoot, join3(path, ".project-context-directory-guard"));
|
|
7130
7272
|
let stat;
|
|
@@ -7295,7 +7437,7 @@ function acquireWorkspaceLock(workspaceRoot, lockPath, afterOpen, beforeStaleRem
|
|
|
7295
7437
|
process_start_id: processStartIdentityLookup(process.pid)
|
|
7296
7438
|
})}
|
|
7297
7439
|
`;
|
|
7298
|
-
openedContentHash =
|
|
7440
|
+
openedContentHash = sha2562(content);
|
|
7299
7441
|
writeFileSync(fd, content);
|
|
7300
7442
|
fsyncSync(fd);
|
|
7301
7443
|
try {
|
|
@@ -7346,7 +7488,7 @@ function removeOwnedLockByInode(lockPath, identity, expectedHash) {
|
|
|
7346
7488
|
const current = lstatSync(lockPath);
|
|
7347
7489
|
if (current.isSymbolicLink() || current.dev !== identity.dev || current.ino !== identity.ino)
|
|
7348
7490
|
return;
|
|
7349
|
-
if (expectedHash !== undefined &&
|
|
7491
|
+
if (expectedHash !== undefined && sha2562(readFileSync(lockPath, "utf8")) !== expectedHash)
|
|
7350
7492
|
return;
|
|
7351
7493
|
rmSync2(lockPath);
|
|
7352
7494
|
fsyncDirectory(resolve(lockPath, ".."));
|
|
@@ -7363,7 +7505,7 @@ function observeStaleWorkspaceLock(lockPath, workspaceRoot, processStartIdentity
|
|
|
7363
7505
|
} catch {
|
|
7364
7506
|
return null;
|
|
7365
7507
|
}
|
|
7366
|
-
const contentHash =
|
|
7508
|
+
const contentHash = sha2562(content);
|
|
7367
7509
|
if (currentFileHash(lockPath, workspaceRoot) !== contentHash)
|
|
7368
7510
|
return null;
|
|
7369
7511
|
let pid = null;
|
|
@@ -7525,7 +7667,7 @@ function releaseWorkspaceLock(lockPath, lock, workspaceRoot) {
|
|
|
7525
7667
|
created_at: new Date().toISOString()
|
|
7526
7668
|
})}
|
|
7527
7669
|
`;
|
|
7528
|
-
releaseHash =
|
|
7670
|
+
releaseHash = sha2562(releaseContent);
|
|
7529
7671
|
writeFileSync(releaseFd, releaseContent);
|
|
7530
7672
|
fsyncSync(releaseFd);
|
|
7531
7673
|
closeSync(releaseFd);
|
|
@@ -7764,7 +7906,7 @@ function currentFileHash(path, workspaceRoot) {
|
|
|
7764
7906
|
return null;
|
|
7765
7907
|
const relativePath = relativePosix(workspaceRoot, path);
|
|
7766
7908
|
const maxBytes = relativePath === ".hasna/session-render-manifest.json" || relativePath === ".codewith/.hasna/session-render-manifest.json" ? SESSION_COMPATIBILITY_MANIFEST_MAX_BYTES : 256 * 1024;
|
|
7767
|
-
return
|
|
7909
|
+
return sha2562(readUtf8RegularFile(path, workspaceRoot, maxBytes));
|
|
7768
7910
|
}
|
|
7769
7911
|
function hashesStillMatch(expected, workspaceRoot) {
|
|
7770
7912
|
for (const [path, hash] of expected) {
|
|
@@ -7933,8 +8075,8 @@ function removeHashForFingerprint(value) {
|
|
|
7933
8075
|
}
|
|
7934
8076
|
return copy;
|
|
7935
8077
|
}
|
|
7936
|
-
function
|
|
7937
|
-
return
|
|
8078
|
+
function sha2562(content) {
|
|
8079
|
+
return createHash2("sha256").update(content).digest("hex");
|
|
7938
8080
|
}
|
|
7939
8081
|
function isRecord(value) {
|
|
7940
8082
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
@@ -8078,7 +8220,7 @@ var CODEWITH_FLATTENED_ADAPTER = {
|
|
|
8078
8220
|
tool: "codewith",
|
|
8079
8221
|
mode: "flattened-markdown",
|
|
8080
8222
|
indexFile: "CODEWITH.md",
|
|
8081
|
-
managedDir:
|
|
8223
|
+
managedDir: SESSION_RENDER_INSTRUCTIONS_MANAGED_DIR,
|
|
8082
8224
|
envVar: "CODEWITH_HOME",
|
|
8083
8225
|
nativeImports: false,
|
|
8084
8226
|
description: "Codewith CODEWITH.md flattened until native @ imports are implemented in Codewith."
|
|
@@ -8087,7 +8229,7 @@ var CODEWITH_NATIVE_ADAPTER = {
|
|
|
8087
8229
|
tool: "codewith",
|
|
8088
8230
|
mode: "native-imports",
|
|
8089
8231
|
indexFile: "CODEWITH.md",
|
|
8090
|
-
managedDir:
|
|
8232
|
+
managedDir: SESSION_RENDER_INSTRUCTIONS_MANAGED_DIR,
|
|
8091
8233
|
envVar: "CODEWITH_HOME",
|
|
8092
8234
|
nativeImports: true,
|
|
8093
8235
|
description: "Codewith CODEWITH.md with gated @ imports into managed fragments."
|
|
@@ -8097,7 +8239,7 @@ var SESSION_TOOL_ADAPTERS = {
|
|
|
8097
8239
|
tool: "claude",
|
|
8098
8240
|
mode: "native-imports",
|
|
8099
8241
|
indexFile: "CLAUDE.md",
|
|
8100
|
-
managedDir:
|
|
8242
|
+
managedDir: SESSION_RENDER_INSTRUCTIONS_MANAGED_DIR,
|
|
8101
8243
|
envVar: "CLAUDE_CONFIG_DIR",
|
|
8102
8244
|
nativeImports: true,
|
|
8103
8245
|
description: "Claude Code CLAUDE.md with @ imports into managed fragments."
|
|
@@ -8106,7 +8248,7 @@ var SESSION_TOOL_ADAPTERS = {
|
|
|
8106
8248
|
tool: "codex",
|
|
8107
8249
|
mode: "flattened-markdown",
|
|
8108
8250
|
indexFile: "AGENTS.md",
|
|
8109
|
-
managedDir:
|
|
8251
|
+
managedDir: SESSION_RENDER_INSTRUCTIONS_MANAGED_DIR,
|
|
8110
8252
|
envVar: "CODEX_HOME",
|
|
8111
8253
|
nativeImports: false,
|
|
8112
8254
|
description: "Codex AGENTS.md flattened instruction file."
|
|
@@ -8123,7 +8265,7 @@ var SESSION_TOOL_ADAPTERS = {
|
|
|
8123
8265
|
mode: "opencode-instructions",
|
|
8124
8266
|
indexFile: "AGENTS.md",
|
|
8125
8267
|
configFile: "opencode.json",
|
|
8126
|
-
managedDir:
|
|
8268
|
+
managedDir: SESSION_RENDER_INSTRUCTIONS_MANAGED_DIR,
|
|
8127
8269
|
envVar: "OPENCODE_CONFIG_DIR",
|
|
8128
8270
|
nativeImports: false,
|
|
8129
8271
|
description: "OpenCode AGENTS.md plus opencode.json instructions pointing at managed fragments."
|
|
@@ -8132,7 +8274,7 @@ var SESSION_TOOL_ADAPTERS = {
|
|
|
8132
8274
|
tool: "aicopilot",
|
|
8133
8275
|
mode: "flattened-markdown",
|
|
8134
8276
|
indexFile: "AICOPILOT.md",
|
|
8135
|
-
managedDir:
|
|
8277
|
+
managedDir: SESSION_RENDER_INSTRUCTIONS_MANAGED_DIR,
|
|
8136
8278
|
envVar: "AICOPILOT_CONFIG_DIR",
|
|
8137
8279
|
nativeImports: false,
|
|
8138
8280
|
description: "AI Copilot AICOPILOT.md flattened instruction file."
|
|
@@ -8155,6 +8297,19 @@ var SESSION_TOOL_ADAPTERS = {
|
|
|
8155
8297
|
},
|
|
8156
8298
|
codewith: CODEWITH_FLATTENED_ADAPTER
|
|
8157
8299
|
};
|
|
8300
|
+
var SESSION_RENDER_MANAGED_DIRS = [
|
|
8301
|
+
...new Set([
|
|
8302
|
+
CODEWITH_FLATTENED_ADAPTER,
|
|
8303
|
+
CODEWITH_NATIVE_ADAPTER,
|
|
8304
|
+
...Object.values(SESSION_TOOL_ADAPTERS)
|
|
8305
|
+
].map((adapter) => adapter.managedDir))
|
|
8306
|
+
];
|
|
8307
|
+
var SESSION_RENDER_SHARED_MANAGED_DIRS = [".cursor/rules"];
|
|
8308
|
+
var SESSION_RENDER_EXCLUSIVE_MANAGED_PATHS = [
|
|
8309
|
+
...SESSION_RENDER_MANAGED_DIRS.filter((dir) => !SESSION_RENDER_SHARED_MANAGED_DIRS.includes(dir)),
|
|
8310
|
+
SESSION_RENDER_MANIFEST_RELATIVE_PATH,
|
|
8311
|
+
SESSION_RENDER_SNAPSHOT_RELATIVE_DIR
|
|
8312
|
+
];
|
|
8158
8313
|
var SESSION_LAYER_RANK = {
|
|
8159
8314
|
global: 10,
|
|
8160
8315
|
tool: 20,
|
|
@@ -8184,11 +8339,11 @@ function ensureTrailingNewline3(content) {
|
|
|
8184
8339
|
`) ? content : `${content}
|
|
8185
8340
|
`;
|
|
8186
8341
|
}
|
|
8187
|
-
function
|
|
8188
|
-
return
|
|
8342
|
+
function sha2563(content) {
|
|
8343
|
+
return createHash3("sha256").update(content).digest("hex");
|
|
8189
8344
|
}
|
|
8190
8345
|
function fingerprint(value) {
|
|
8191
|
-
return
|
|
8346
|
+
return sha2563(JSON.stringify(value));
|
|
8192
8347
|
}
|
|
8193
8348
|
function canonicalFingerprintValue(value) {
|
|
8194
8349
|
if (Array.isArray(value))
|
|
@@ -8254,18 +8409,59 @@ function makeFile(targetHome, relativePath, role, content, sourceIds) {
|
|
|
8254
8409
|
relativePath: safeRelativePath,
|
|
8255
8410
|
role,
|
|
8256
8411
|
content: normalizedContent,
|
|
8257
|
-
sha256:
|
|
8412
|
+
sha256: sha2563(normalizedContent),
|
|
8258
8413
|
sourceIds
|
|
8259
8414
|
};
|
|
8260
8415
|
}
|
|
8416
|
+
function claimsAgentOperatingRulesPolicy(source, content) {
|
|
8417
|
+
if (!AGENT_OPERATING_RULES_SENTINEL_PATTERN.test(content))
|
|
8418
|
+
return false;
|
|
8419
|
+
if (source.nonOverridable === true)
|
|
8420
|
+
return true;
|
|
8421
|
+
if (source.id === GLOBAL_AGENT_RULES_STANDARD_SLUG || source.id === AGENT_OPERATING_RULES_SOURCE_ID)
|
|
8422
|
+
return true;
|
|
8423
|
+
if (source.metadata?.["role"] === AGENT_OPERATING_RULES_ROLE)
|
|
8424
|
+
return true;
|
|
8425
|
+
return AGENT_OPERATING_RULES_HEADING_PATTERN.test(content.trimStart());
|
|
8426
|
+
}
|
|
8427
|
+
function applyAgentOperatingRulesFloor(source, content) {
|
|
8428
|
+
const unchanged = {
|
|
8429
|
+
content,
|
|
8430
|
+
provenance: source.provenance ?? null,
|
|
8431
|
+
metadata: source.metadata ?? null
|
|
8432
|
+
};
|
|
8433
|
+
if (!claimsAgentOperatingRulesPolicy(source, content))
|
|
8434
|
+
return unchanged;
|
|
8435
|
+
const payload = resolveAgentOperatingRulesPayload(content);
|
|
8436
|
+
if (payload.content === content) {
|
|
8437
|
+
return {
|
|
8438
|
+
content,
|
|
8439
|
+
provenance: { ...source.provenance ?? {}, payloadIntegrity: payload.integrity },
|
|
8440
|
+
metadata: { ...source.metadata ?? {}, payloadIntegrity: payload.integrity }
|
|
8441
|
+
};
|
|
8442
|
+
}
|
|
8443
|
+
const floored = {
|
|
8444
|
+
payloadFloorApplied: true,
|
|
8445
|
+
flooredFromRulesVersion: parseAgentOperatingRulesVersion(content),
|
|
8446
|
+
flooredFromPayloadSha256: sha2563(content)
|
|
8447
|
+
};
|
|
8448
|
+
return {
|
|
8449
|
+
content: payload.content,
|
|
8450
|
+
provenance: { ...source.provenance ?? {}, ...payload.provenance, ...floored },
|
|
8451
|
+
metadata: { ...source.metadata ?? {}, ...payload.metadata, ...floored }
|
|
8452
|
+
};
|
|
8453
|
+
}
|
|
8261
8454
|
function normalizeSources(sources, tool, allowEmptySources) {
|
|
8262
8455
|
const normalized = sources.map((source, index) => {
|
|
8263
8456
|
if (!source.id.trim())
|
|
8264
8457
|
throw new Error("Session instruction source id is required.");
|
|
8265
|
-
const
|
|
8458
|
+
const floored = applyAgentOperatingRulesFloor(source, source.content ?? "");
|
|
8459
|
+
const content = filterProviderOnlyBlocks(floored.content, tool);
|
|
8266
8460
|
const normalized2 = {
|
|
8267
8461
|
...source,
|
|
8268
8462
|
content,
|
|
8463
|
+
provenance: floored.provenance,
|
|
8464
|
+
metadata: floored.metadata,
|
|
8269
8465
|
normalizedId: slug(source.id),
|
|
8270
8466
|
resolvedLabel: source.label ?? source.id,
|
|
8271
8467
|
resolvedLayer: source.layer === undefined ? "agent" : normalizeSessionInstructionLayer(source.layer),
|
|
@@ -8288,30 +8484,36 @@ function deduplicateSemanticPolicySources(sources) {
|
|
|
8288
8484
|
const selected = [];
|
|
8289
8485
|
const policySources = new Map;
|
|
8290
8486
|
for (const source of sources) {
|
|
8291
|
-
const sentinel = source.content.match(
|
|
8487
|
+
const sentinel = source.content.match(AGENT_OPERATING_RULES_SENTINEL_PATTERN);
|
|
8292
8488
|
if (!sentinel) {
|
|
8293
8489
|
selected.push(source);
|
|
8294
8490
|
continue;
|
|
8295
8491
|
}
|
|
8296
|
-
const
|
|
8492
|
+
const version = sentinel[1];
|
|
8493
|
+
const key = AGENT_OPERATING_RULES_SEMANTIC_POLICY_KEY;
|
|
8297
8494
|
const normalizedContent = source.content.replace(/\r\n/g, `
|
|
8298
8495
|
`).trim();
|
|
8299
8496
|
const existing = policySources.get(key);
|
|
8300
8497
|
if (!existing) {
|
|
8301
|
-
policySources.set(key, { index: selected.length, normalizedContent });
|
|
8498
|
+
policySources.set(key, { index: selected.length, version, normalizedContent });
|
|
8302
8499
|
selected.push(source);
|
|
8303
8500
|
continue;
|
|
8304
8501
|
}
|
|
8305
|
-
|
|
8306
|
-
|
|
8502
|
+
const versionOrder = compareAgentOperatingRulesVersions(version, existing.version);
|
|
8503
|
+
if (versionOrder === 0 && existing.normalizedContent !== normalizedContent) {
|
|
8504
|
+
throw new Error(`Conflicting semantic policy sources declare ${key}/v${version} with different content.`);
|
|
8307
8505
|
}
|
|
8308
8506
|
const current = selected[existing.index];
|
|
8309
|
-
|
|
8507
|
+
const priorityOrder = semanticPolicySourcePriority(source) - semanticPolicySourcePriority(current);
|
|
8508
|
+
if (priorityOrder < 0)
|
|
8509
|
+
continue;
|
|
8510
|
+
if (priorityOrder === 0 && versionOrder <= 0)
|
|
8310
8511
|
continue;
|
|
8311
8512
|
selected[existing.index] = {
|
|
8312
8513
|
...source,
|
|
8313
8514
|
resolvedOrder: current.resolvedOrder
|
|
8314
8515
|
};
|
|
8516
|
+
policySources.set(key, { index: existing.index, version, normalizedContent });
|
|
8315
8517
|
}
|
|
8316
8518
|
return selected;
|
|
8317
8519
|
}
|
|
@@ -8321,7 +8523,7 @@ function semanticPolicySourcePriority(source) {
|
|
|
8321
8523
|
priority += 4;
|
|
8322
8524
|
if (source.id === GLOBAL_AGENT_RULES_STANDARD_SLUG)
|
|
8323
8525
|
priority += 2;
|
|
8324
|
-
if (source.metadata?.["role"] ===
|
|
8526
|
+
if (source.metadata?.["role"] === AGENT_OPERATING_RULES_ROLE)
|
|
8325
8527
|
priority += 1;
|
|
8326
8528
|
return priority;
|
|
8327
8529
|
}
|
|
@@ -8810,7 +9012,7 @@ function planSessionRender(input) {
|
|
|
8810
9012
|
globs: rule.globs ?? [],
|
|
8811
9013
|
hash: rule.hash ?? null
|
|
8812
9014
|
})),
|
|
8813
|
-
renderedPayloadSha256:
|
|
9015
|
+
renderedPayloadSha256: sha2563(source.content),
|
|
8814
9016
|
provenance: source.provenance ?? null,
|
|
8815
9017
|
metadata: source.metadata ?? null
|
|
8816
9018
|
})),
|
|
@@ -8828,8 +9030,8 @@ function planSessionRender(input) {
|
|
|
8828
9030
|
...input.providerConfig ? {
|
|
8829
9031
|
providerConfig: {
|
|
8830
9032
|
sourceId: input.providerConfig.sourceId,
|
|
8831
|
-
selectedPayloadSha256:
|
|
8832
|
-
renderedPayloadSha256: files.find((file) => file.relativePath === adapter.configFile)?.sha256 ??
|
|
9033
|
+
selectedPayloadSha256: sha2563(input.providerConfig.content),
|
|
9034
|
+
renderedPayloadSha256: files.find((file) => file.relativePath === adapter.configFile)?.sha256 ?? sha2563(input.providerConfig.content),
|
|
8833
9035
|
selected: !existsSync4(joinTarget(targetHome, adapter.configFile))
|
|
8834
9036
|
}
|
|
8835
9037
|
} : {},
|
|
@@ -8873,15 +9075,16 @@ function sourceFromFilePath(path, content, order = 0) {
|
|
|
8873
9075
|
}
|
|
8874
9076
|
function sourceFromConfig(config, order = 0, layer) {
|
|
8875
9077
|
const isAgentOperatingRules = config.slug === GLOBAL_AGENT_RULES_STANDARD_SLUG;
|
|
9078
|
+
const rules = isAgentOperatingRules ? resolveAgentOperatingRulesPayload(config.content) : null;
|
|
8876
9079
|
return {
|
|
8877
9080
|
id: config.slug,
|
|
8878
9081
|
label: config.name,
|
|
8879
|
-
content:
|
|
9082
|
+
content: rules ? rules.content : config.content,
|
|
8880
9083
|
layer: layer ?? (config.agent === "global" ? "global" : "agent"),
|
|
8881
9084
|
order,
|
|
8882
9085
|
path: config.target_path ?? undefined,
|
|
8883
|
-
provenance:
|
|
8884
|
-
...
|
|
9086
|
+
provenance: rules ? {
|
|
9087
|
+
...rules.provenance,
|
|
8885
9088
|
configSlug: config.slug,
|
|
8886
9089
|
configAgent: config.agent
|
|
8887
9090
|
} : {
|
|
@@ -8889,7 +9092,7 @@ function sourceFromConfig(config, order = 0, layer) {
|
|
|
8889
9092
|
configSlug: config.slug,
|
|
8890
9093
|
configAgent: config.agent
|
|
8891
9094
|
},
|
|
8892
|
-
metadata:
|
|
9095
|
+
metadata: rules ? { ...rules.metadata } : null,
|
|
8893
9096
|
nonOverridable: isAgentOperatingRules
|
|
8894
9097
|
};
|
|
8895
9098
|
}
|
|
@@ -8927,7 +9130,7 @@ function selectProfileConfigsForSessionRender(configs, tool) {
|
|
|
8927
9130
|
const selectedSources = [];
|
|
8928
9131
|
const equivalentSources = new Map;
|
|
8929
9132
|
for (const candidate of sources) {
|
|
8930
|
-
const key =
|
|
9133
|
+
const key = sha2563(candidate.source.content);
|
|
8931
9134
|
const existing = equivalentSources.get(key);
|
|
8932
9135
|
if (!existing) {
|
|
8933
9136
|
equivalentSources.set(key, {
|
|
@@ -9273,6 +9476,77 @@ function asStringArray(value) {
|
|
|
9273
9476
|
return value.filter((item) => typeof item === "string");
|
|
9274
9477
|
}
|
|
9275
9478
|
|
|
9479
|
+
// src/lib/session-render-ownership.ts
|
|
9480
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
|
|
9481
|
+
import { dirname as dirname3, join as join5, parse as parse3, relative as relative3, sep } from "path";
|
|
9482
|
+
var MANIFEST_ANCESTOR_LIMIT = 24;
|
|
9483
|
+
var MANAGED_PATH_SEGMENTS = SESSION_RENDER_EXCLUSIVE_MANAGED_PATHS.map((managedPath) => managedPath.split("/").filter(Boolean));
|
|
9484
|
+
var manifestCache = new Map;
|
|
9485
|
+
function toSegments(absolutePath2) {
|
|
9486
|
+
return absolutePath2.replaceAll("\\", "/").split("/").filter(Boolean);
|
|
9487
|
+
}
|
|
9488
|
+
function pathIsSessionRenderManagedDir(absolutePath2) {
|
|
9489
|
+
const segments = toSegments(absolutePath2);
|
|
9490
|
+
return MANAGED_PATH_SEGMENTS.some((managed) => {
|
|
9491
|
+
if (managed.length === 0 || managed.length > segments.length)
|
|
9492
|
+
return false;
|
|
9493
|
+
for (let start = 0;start + managed.length <= segments.length; start += 1) {
|
|
9494
|
+
if (managed.every((segment, offset) => segments[start + offset] === segment))
|
|
9495
|
+
return true;
|
|
9496
|
+
}
|
|
9497
|
+
return false;
|
|
9498
|
+
});
|
|
9499
|
+
}
|
|
9500
|
+
function readManifestRelativePaths(manifestPath) {
|
|
9501
|
+
let stats;
|
|
9502
|
+
try {
|
|
9503
|
+
if (!existsSync5(manifestPath))
|
|
9504
|
+
return null;
|
|
9505
|
+
stats = statSync3(manifestPath);
|
|
9506
|
+
} catch {
|
|
9507
|
+
return null;
|
|
9508
|
+
}
|
|
9509
|
+
const cached = manifestCache.get(manifestPath);
|
|
9510
|
+
if (cached && cached.mtimeMs === stats.mtimeMs && cached.size === stats.size) {
|
|
9511
|
+
return cached.relativePaths;
|
|
9512
|
+
}
|
|
9513
|
+
let manifest;
|
|
9514
|
+
try {
|
|
9515
|
+
manifest = JSON.parse(readFileSync3(manifestPath, "utf-8"));
|
|
9516
|
+
} catch {
|
|
9517
|
+
return null;
|
|
9518
|
+
}
|
|
9519
|
+
if (manifest?.schema !== SESSION_RENDER_SCHEMA || !Array.isArray(manifest.files))
|
|
9520
|
+
return null;
|
|
9521
|
+
const writerId = manifest.targetOwner?.writer?.id;
|
|
9522
|
+
if (writerId !== undefined && writerId !== SESSION_RENDERER_OWNER_ID)
|
|
9523
|
+
return null;
|
|
9524
|
+
const relativePaths = new Set(manifest.files.map((file) => file?.relativePath).filter((relativePath) => typeof relativePath === "string").map((relativePath) => relativePath.replaceAll("\\", "/")));
|
|
9525
|
+
manifestCache.set(manifestPath, { mtimeMs: stats.mtimeMs, size: stats.size, relativePaths });
|
|
9526
|
+
return relativePaths;
|
|
9527
|
+
}
|
|
9528
|
+
function sessionRenderManifestClaimsPath(absolutePath2) {
|
|
9529
|
+
const root = parse3(absolutePath2).root;
|
|
9530
|
+
let home = dirname3(absolutePath2);
|
|
9531
|
+
for (let depth = 0;depth < MANIFEST_ANCESTOR_LIMIT; depth += 1) {
|
|
9532
|
+
const manifestPath = join5(home, ...SESSION_RENDER_MANIFEST_RELATIVE_PATH.split("/"));
|
|
9533
|
+
const relativePaths = readManifestRelativePaths(manifestPath);
|
|
9534
|
+
if (relativePaths) {
|
|
9535
|
+
const claimed = relative3(home, absolutePath2).split(sep).join("/");
|
|
9536
|
+
if (relativePaths.has(claimed))
|
|
9537
|
+
return true;
|
|
9538
|
+
}
|
|
9539
|
+
const parent = dirname3(home);
|
|
9540
|
+
if (parent === home || home === root)
|
|
9541
|
+
break;
|
|
9542
|
+
home = parent;
|
|
9543
|
+
}
|
|
9544
|
+
return false;
|
|
9545
|
+
}
|
|
9546
|
+
function sessionRenderOwnsPath(absolutePath2) {
|
|
9547
|
+
return pathIsSessionRenderManagedDir(absolutePath2) || sessionRenderManifestClaimsPath(absolutePath2);
|
|
9548
|
+
}
|
|
9549
|
+
|
|
9276
9550
|
// src/lib/apply.ts
|
|
9277
9551
|
function getConfigHome() {
|
|
9278
9552
|
return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir3();
|
|
@@ -9291,14 +9565,14 @@ function normalizeTargetPath(p) {
|
|
|
9291
9565
|
let current = expanded;
|
|
9292
9566
|
const missingSegments = [];
|
|
9293
9567
|
while (true) {
|
|
9294
|
-
if (
|
|
9568
|
+
if (existsSync6(current)) {
|
|
9295
9569
|
try {
|
|
9296
9570
|
return resolve3(realpathSync2(current), ...missingSegments);
|
|
9297
9571
|
} catch {
|
|
9298
9572
|
return expanded;
|
|
9299
9573
|
}
|
|
9300
9574
|
}
|
|
9301
|
-
const parent =
|
|
9575
|
+
const parent = dirname4(current);
|
|
9302
9576
|
const name = basename4(current);
|
|
9303
9577
|
if (parent === current)
|
|
9304
9578
|
return expanded;
|
|
@@ -9320,11 +9594,11 @@ async function writeConfigResult(config, targetPath, content, opts, meta = {}) {
|
|
|
9320
9594
|
throw new ConfigApplyError(`Antigravity rule file ${renderedTargetPath} is ${renderedContent.length} characters; split it before applying because Antigravity limits rule files to ${ANTIGRAVITY_RULE_FILE_CHAR_LIMIT} characters.`);
|
|
9321
9595
|
}
|
|
9322
9596
|
const path = expandPath(renderedTargetPath);
|
|
9323
|
-
const previousContent =
|
|
9597
|
+
const previousContent = existsSync6(path) ? readFileSync4(path, "utf-8") : null;
|
|
9324
9598
|
const changed = previousContent !== renderedContent;
|
|
9325
9599
|
if (!opts.dryRun) {
|
|
9326
|
-
const dir =
|
|
9327
|
-
if (!
|
|
9600
|
+
const dir = dirname4(path);
|
|
9601
|
+
if (!existsSync6(dir)) {
|
|
9328
9602
|
mkdirSync3(dir, { recursive: true });
|
|
9329
9603
|
}
|
|
9330
9604
|
if (previousContent !== null && changed) {
|
|
@@ -9627,35 +9901,37 @@ function sessionRendererOwnsTarget(targetPath, opts) {
|
|
|
9627
9901
|
return sessionRendererOwnsCanonicalTarget(canonicalApplyTargetPath(targetPath, opts), opts);
|
|
9628
9902
|
}
|
|
9629
9903
|
function sessionRendererOwnsCanonicalTarget(normalized, opts) {
|
|
9904
|
+
if (opts.allowSessionRendererOwned)
|
|
9905
|
+
return false;
|
|
9630
9906
|
const homes = new Set([
|
|
9631
9907
|
getConfigHome(),
|
|
9632
9908
|
opts.vars?.["HOME_DIR"]
|
|
9633
9909
|
].filter((home) => typeof home === "string" && home.length > 0));
|
|
9634
|
-
if ([...homes].some((home) => SESSION_RENDER_OWNED_CONFIG_TARGETS.some((relativePath) => normalized === normalizeTargetPath(
|
|
9910
|
+
if ([...homes].some((home) => SESSION_RENDER_OWNED_CONFIG_TARGETS.some((relativePath) => normalized === normalizeTargetPath(join6(home, ...relativePath.split("/"))))))
|
|
9635
9911
|
return true;
|
|
9636
|
-
return normalized
|
|
9912
|
+
return sessionRenderOwnsPath(normalized);
|
|
9637
9913
|
}
|
|
9638
9914
|
|
|
9639
9915
|
// src/lib/package-version.ts
|
|
9640
|
-
import { existsSync as
|
|
9641
|
-
import { dirname as
|
|
9916
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
|
|
9917
|
+
import { dirname as dirname5, join as join7 } from "path";
|
|
9642
9918
|
import { fileURLToPath } from "url";
|
|
9643
9919
|
var cached = null;
|
|
9644
9920
|
function getPackageVersion() {
|
|
9645
9921
|
if (cached)
|
|
9646
9922
|
return cached;
|
|
9647
9923
|
try {
|
|
9648
|
-
let dir =
|
|
9924
|
+
let dir = dirname5(fileURLToPath(import.meta.url));
|
|
9649
9925
|
for (let i = 0;i < 8; i++) {
|
|
9650
|
-
const pkgPath =
|
|
9651
|
-
if (
|
|
9652
|
-
const pkg = JSON.parse(
|
|
9926
|
+
const pkgPath = join7(dir, "package.json");
|
|
9927
|
+
if (existsSync7(pkgPath)) {
|
|
9928
|
+
const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
|
|
9653
9929
|
if (pkg.name === "@hasna/instructions" && pkg.version) {
|
|
9654
9930
|
cached = pkg.version;
|
|
9655
9931
|
return cached;
|
|
9656
9932
|
}
|
|
9657
9933
|
}
|
|
9658
|
-
const parent =
|
|
9934
|
+
const parent = dirname5(dir);
|
|
9659
9935
|
if (parent === dir)
|
|
9660
9936
|
break;
|
|
9661
9937
|
dir = parent;
|
|
@@ -9711,11 +9987,11 @@ async function getConfigsStatus(store = resolveConfigStore()) {
|
|
|
9711
9987
|
continue;
|
|
9712
9988
|
knownTargets += 1;
|
|
9713
9989
|
const targetPath = expandPath(config.target_path);
|
|
9714
|
-
if (!
|
|
9990
|
+
if (!existsSync8(targetPath)) {
|
|
9715
9991
|
missingTargets += 1;
|
|
9716
9992
|
continue;
|
|
9717
9993
|
}
|
|
9718
|
-
const disk =
|
|
9994
|
+
const disk = readFileSync6(targetPath, "utf-8");
|
|
9719
9995
|
const { content: redactedDisk } = redactContent(disk, config.format);
|
|
9720
9996
|
if (redactedDisk !== config.content) {
|
|
9721
9997
|
driftedTargets += 1;
|
|
@@ -9858,16 +10134,16 @@ var PG_MIGRATIONS = [
|
|
|
9858
10134
|
`ALTER TABLE configs ADD COLUMN IF NOT EXISTS outputs TEXT NOT NULL DEFAULT '[]'`
|
|
9859
10135
|
];
|
|
9860
10136
|
// src/lib/session-apply.ts
|
|
9861
|
-
import { createHash as
|
|
10137
|
+
import { createHash as createHash4, randomUUID as randomUUID4 } from "crypto";
|
|
9862
10138
|
import {
|
|
9863
|
-
existsSync as
|
|
10139
|
+
existsSync as existsSync9,
|
|
9864
10140
|
lstatSync as lstatSync2,
|
|
9865
10141
|
mkdirSync as mkdirSync4,
|
|
9866
|
-
readFileSync as
|
|
10142
|
+
readFileSync as readFileSync7,
|
|
9867
10143
|
readdirSync,
|
|
9868
|
-
statSync as
|
|
10144
|
+
statSync as statSync4
|
|
9869
10145
|
} from "fs";
|
|
9870
|
-
import { dirname as
|
|
10146
|
+
import { dirname as dirname6, isAbsolute as isAbsolute3, join as join8, parse as parse4, relative as relative4, resolve as resolve4 } from "path";
|
|
9871
10147
|
class SessionApplyError extends Error {
|
|
9872
10148
|
constructor(message) {
|
|
9873
10149
|
super(message);
|
|
@@ -9947,13 +10223,13 @@ function applySessionRenderUnlocked(plan, options, coordination) {
|
|
|
9947
10223
|
};
|
|
9948
10224
|
}
|
|
9949
10225
|
function ensureSessionTargetHome(targetHome) {
|
|
9950
|
-
if (!
|
|
10226
|
+
if (!existsSync9(targetHome))
|
|
9951
10227
|
mkdirSync4(targetHome, { recursive: true, mode: 448 });
|
|
9952
10228
|
assertSafeTargetHome(targetHome);
|
|
9953
10229
|
}
|
|
9954
10230
|
function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
9955
10231
|
const safeTargetHome = assertSafeTargetHome(targetHome);
|
|
9956
|
-
const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(
|
|
10232
|
+
const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(relative4(safeTargetHome, resolve4(manifestPath)), safeTargetHome) : resolve4(safeTargetHome, ".hasna", "session-render-manifest.json");
|
|
9957
10233
|
const checkedAt = new Date().toISOString();
|
|
9958
10234
|
const previousManifest = readPreviousManifest(resolvedManifestPath);
|
|
9959
10235
|
if (!previousManifest) {
|
|
@@ -9970,7 +10246,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
|
9970
10246
|
const drifted = [];
|
|
9971
10247
|
for (const file of previousManifest.files) {
|
|
9972
10248
|
const target = resolveManifestRelativePath(file.relativePath, safeTargetHome);
|
|
9973
|
-
if (!
|
|
10249
|
+
if (!existsSync9(target)) {
|
|
9974
10250
|
missing.push({
|
|
9975
10251
|
path: target,
|
|
9976
10252
|
relativePath: file.relativePath,
|
|
@@ -9980,7 +10256,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
|
9980
10256
|
});
|
|
9981
10257
|
continue;
|
|
9982
10258
|
}
|
|
9983
|
-
const actualSha256 =
|
|
10259
|
+
const actualSha256 = sha2564(readFileSync7(target, "utf-8"));
|
|
9984
10260
|
if (actualSha256 !== file.sha256) {
|
|
9985
10261
|
drifted.push({
|
|
9986
10262
|
path: target,
|
|
@@ -10004,7 +10280,7 @@ function restoreSessionRenderSnapshot(snapshotPath, options = {}) {
|
|
|
10004
10280
|
const snapshot = readSessionRenderSnapshot(snapshotPath);
|
|
10005
10281
|
const targetHome = assertSafeTargetHome(snapshot.targetHome);
|
|
10006
10282
|
const resolvedSnapshotPath = resolve4(snapshotPath);
|
|
10007
|
-
const snapshotRelativePath =
|
|
10283
|
+
const snapshotRelativePath = relative4(targetHome, resolvedSnapshotPath);
|
|
10008
10284
|
if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") || isAbsolute3(snapshotRelativePath)) {
|
|
10009
10285
|
throw new SessionApplyError("Session snapshot must be stored inside its target home.");
|
|
10010
10286
|
}
|
|
@@ -10122,18 +10398,18 @@ function requiredRestoreHash(file) {
|
|
|
10122
10398
|
}
|
|
10123
10399
|
function readSessionRenderSnapshot(snapshotPath) {
|
|
10124
10400
|
const resolved = resolve4(snapshotPath);
|
|
10125
|
-
if (!
|
|
10401
|
+
if (!existsSync9(resolved))
|
|
10126
10402
|
throw new SessionApplyError(`Session snapshot not found: ${snapshotPath}`);
|
|
10127
10403
|
const stat = lstatSync2(resolved);
|
|
10128
10404
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
10129
10405
|
throw new SessionApplyError(`Session snapshot is not a regular file: ${snapshotPath}`);
|
|
10130
10406
|
}
|
|
10131
|
-
if (
|
|
10407
|
+
if (statSync4(resolved).size > 32 * 1024 * 1024) {
|
|
10132
10408
|
throw new SessionApplyError(`Session snapshot exceeds the 32 MiB restore limit: ${snapshotPath}`);
|
|
10133
10409
|
}
|
|
10134
10410
|
let parsed;
|
|
10135
10411
|
try {
|
|
10136
|
-
parsed = JSON.parse(
|
|
10412
|
+
parsed = JSON.parse(readFileSync7(resolved, "utf8"));
|
|
10137
10413
|
} catch {
|
|
10138
10414
|
throw new SessionApplyError(`Session snapshot is not valid JSON: ${snapshotPath}`);
|
|
10139
10415
|
}
|
|
@@ -10155,7 +10431,7 @@ function readSessionRenderSnapshot(snapshotPath) {
|
|
|
10155
10431
|
const previousManifest = snapshot.previousManifest;
|
|
10156
10432
|
const previousFiles = new Map;
|
|
10157
10433
|
for (const file of snapshot.files) {
|
|
10158
|
-
if (!file || typeof file.relativePath !== "string" || typeof file.path !== "string" || typeof file.sha256 !== "string" || typeof file.content !== "string" ||
|
|
10434
|
+
if (!file || typeof file.relativePath !== "string" || typeof file.path !== "string" || typeof file.sha256 !== "string" || typeof file.content !== "string" || sha2564(file.content) !== file.sha256) {
|
|
10159
10435
|
throw new SessionApplyError(`Session snapshot previous file metadata is invalid: ${snapshotPath}`);
|
|
10160
10436
|
}
|
|
10161
10437
|
resolveSnapshotFilePath(file.relativePath, file.path, targetHome);
|
|
@@ -10203,7 +10479,7 @@ function readSessionRenderSnapshot(snapshotPath) {
|
|
|
10203
10479
|
function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previousManifestFiles, targetHome, snapshotPath) {
|
|
10204
10480
|
assertNoNewerSessionSnapshot(snapshotPath, snapshot.createdAt, targetHome);
|
|
10205
10481
|
const manifestPath = resolve4(snapshot.manifestPath);
|
|
10206
|
-
const manifestRelativePath =
|
|
10482
|
+
const manifestRelativePath = relative4(targetHome, manifestPath).replaceAll("\\", "/");
|
|
10207
10483
|
resolveSnapshotFilePath(manifestRelativePath, snapshot.manifestPath, targetHome);
|
|
10208
10484
|
const manifestSha256 = currentSessionFileHash(manifestPath, targetHome);
|
|
10209
10485
|
if (manifestSha256 === null) {
|
|
@@ -10211,7 +10487,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
|
|
|
10211
10487
|
}
|
|
10212
10488
|
let parsedManifest;
|
|
10213
10489
|
try {
|
|
10214
|
-
parsedManifest = JSON.parse(
|
|
10490
|
+
parsedManifest = JSON.parse(readFileSync7(manifestPath, "utf8"));
|
|
10215
10491
|
} catch {
|
|
10216
10492
|
throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is not valid JSON: ${snapshotPath}`);
|
|
10217
10493
|
}
|
|
@@ -10303,15 +10579,15 @@ function assertNoNewerSessionSnapshot(snapshotPath, createdAt, targetHome) {
|
|
|
10303
10579
|
if (!Number.isFinite(createdAtMs)) {
|
|
10304
10580
|
throw new SessionApplyError(`Pre-rollback legacy v1 snapshot has an invalid creation time: ${snapshotPath}`);
|
|
10305
10581
|
}
|
|
10306
|
-
for (const entry of readdirSync(
|
|
10307
|
-
const candidatePath = resolve4(
|
|
10582
|
+
for (const entry of readdirSync(dirname6(snapshotPath))) {
|
|
10583
|
+
const candidatePath = resolve4(dirname6(snapshotPath), entry);
|
|
10308
10584
|
if (candidatePath === resolve4(snapshotPath) || !entry.endsWith(".json"))
|
|
10309
10585
|
continue;
|
|
10310
10586
|
const candidateStat = lstatSync2(candidatePath);
|
|
10311
10587
|
if (candidateStat.isSymbolicLink() || !candidateStat.isFile() || candidateStat.size > 32 * 1024 * 1024)
|
|
10312
10588
|
continue;
|
|
10313
10589
|
try {
|
|
10314
|
-
const candidate = JSON.parse(
|
|
10590
|
+
const candidate = JSON.parse(readFileSync7(candidatePath, "utf8"));
|
|
10315
10591
|
const candidateCreatedAtMs = typeof candidate.createdAt === "string" ? Date.parse(candidate.createdAt) : Number.NaN;
|
|
10316
10592
|
if ((candidate.schema === "hasna.configs.session-render-snapshot/v1" || candidate.schema === "hasna.configs.session-render-snapshot/v2") && typeof candidate.targetHome === "string" && resolve4(candidate.targetHome) === targetHome && Number.isFinite(candidateCreatedAtMs) && candidateCreatedAtMs >= createdAtMs) {
|
|
10317
10593
|
throw new SessionApplyError(`Cannot restore pre-rollback legacy v1 snapshot after a newer session snapshot exists: ${candidatePath}`);
|
|
@@ -10371,7 +10647,7 @@ function inferLegacySnapshotAction(file, previousFiles, previousManifestFiles, p
|
|
|
10371
10647
|
return "create";
|
|
10372
10648
|
}
|
|
10373
10649
|
if (file.role === "manifest" && previousManifest) {
|
|
10374
|
-
const previousManifestSha256 =
|
|
10650
|
+
const previousManifestSha256 = sha2564(`${JSON.stringify(previousManifest, null, 2)}
|
|
10375
10651
|
`);
|
|
10376
10652
|
if (previousManifestSha256 !== file.sha256) {
|
|
10377
10653
|
throw new SessionApplyError(`Cannot infer legacy v1 manifest action without a before-image: ${file.relativePath}`);
|
|
@@ -10389,8 +10665,8 @@ function resolveSnapshotFilePath(relativePath, recordedPath, targetHome) {
|
|
|
10389
10665
|
}
|
|
10390
10666
|
function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options) {
|
|
10391
10667
|
const target = resolvePlannedFilePath(plan, file, targetHome);
|
|
10392
|
-
const previousContent =
|
|
10393
|
-
const previousSha256 = previousContent === null ? null :
|
|
10668
|
+
const previousContent = existsSync9(target) ? readFileSync7(target, "utf-8") : null;
|
|
10669
|
+
const previousSha256 = previousContent === null ? null : sha2564(previousContent);
|
|
10394
10670
|
const previouslyManaged = isPreviouslyManaged(file, previousSha256, previousHashes, previousManifest);
|
|
10395
10671
|
const changed = previousContent !== file.content;
|
|
10396
10672
|
if (previousContent !== null && !options.force && !previouslyManaged) {
|
|
@@ -10472,10 +10748,10 @@ function planStaleFileResults(plan, targetHome, previousManifest, currentRelativ
|
|
|
10472
10748
|
}
|
|
10473
10749
|
function planStaleFileResult(file, targetHome, options) {
|
|
10474
10750
|
const target = resolveManifestRelativePath(file.relativePath, targetHome);
|
|
10475
|
-
if (!
|
|
10751
|
+
if (!existsSync9(target))
|
|
10476
10752
|
return null;
|
|
10477
|
-
const previousContent =
|
|
10478
|
-
const previousSha256 =
|
|
10753
|
+
const previousContent = readFileSync7(target, "utf-8");
|
|
10754
|
+
const previousSha256 = sha2564(previousContent);
|
|
10479
10755
|
if (!options.force && previousSha256 !== file.sha256) {
|
|
10480
10756
|
return {
|
|
10481
10757
|
path: target,
|
|
@@ -10520,7 +10796,7 @@ function isPreviouslyManaged(file, previousSha256, previousHashes, previousManif
|
|
|
10520
10796
|
}
|
|
10521
10797
|
function resolvePlannedFilePath(plan, file, targetHome) {
|
|
10522
10798
|
const target = resolve4(targetHome, ...file.relativePath.split("/"));
|
|
10523
|
-
const rel =
|
|
10799
|
+
const rel = relative4(targetHome, target);
|
|
10524
10800
|
if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute3(rel)) {
|
|
10525
10801
|
throw new SessionApplyError(`Session file escapes target home: ${file.relativePath}`);
|
|
10526
10802
|
}
|
|
@@ -10532,7 +10808,7 @@ function resolvePlannedFilePath(plan, file, targetHome) {
|
|
|
10532
10808
|
}
|
|
10533
10809
|
function resolveManifestRelativePath(relativePath, targetHome) {
|
|
10534
10810
|
const target = resolve4(targetHome, ...relativePath.split(/[\\/]+/));
|
|
10535
|
-
const rel =
|
|
10811
|
+
const rel = relative4(targetHome, target);
|
|
10536
10812
|
if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute3(rel)) {
|
|
10537
10813
|
throw new SessionApplyError(`Session manifest file escapes target home: ${relativePath}`);
|
|
10538
10814
|
}
|
|
@@ -10540,10 +10816,10 @@ function resolveManifestRelativePath(relativePath, targetHome) {
|
|
|
10540
10816
|
return target;
|
|
10541
10817
|
}
|
|
10542
10818
|
function readPreviousManifest(path) {
|
|
10543
|
-
if (!
|
|
10819
|
+
if (!existsSync9(path))
|
|
10544
10820
|
return null;
|
|
10545
10821
|
try {
|
|
10546
|
-
const parsed = JSON.parse(
|
|
10822
|
+
const parsed = JSON.parse(readFileSync7(path, "utf-8"));
|
|
10547
10823
|
if (parsed.schema !== SESSION_RENDER_SCHEMA)
|
|
10548
10824
|
return null;
|
|
10549
10825
|
if (!Array.isArray(parsed.files))
|
|
@@ -10577,18 +10853,18 @@ function applyPlannedFile(plan, file, targetHome, resultsByPath, coordination, a
|
|
|
10577
10853
|
function assertExpectedSessionFileHash(path, targetHome, expectedHash) {
|
|
10578
10854
|
const actualHash = currentSessionFileHash(path, targetHome);
|
|
10579
10855
|
if (actualHash !== expectedHash) {
|
|
10580
|
-
throw new SessionApplyError(`Session apply path changed after planning: ${
|
|
10856
|
+
throw new SessionApplyError(`Session apply path changed after planning: ${relative4(targetHome, path)}`);
|
|
10581
10857
|
}
|
|
10582
10858
|
}
|
|
10583
10859
|
function currentSessionFileHash(path, targetHome) {
|
|
10584
10860
|
assertNoSymlinkSegments2(targetHome, path);
|
|
10585
|
-
if (!
|
|
10861
|
+
if (!existsSync9(path))
|
|
10586
10862
|
return null;
|
|
10587
10863
|
const stat = lstatSync2(path);
|
|
10588
10864
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
10589
10865
|
throw new SessionApplyError(`Session apply path is not a regular file: ${path}`);
|
|
10590
10866
|
}
|
|
10591
|
-
return
|
|
10867
|
+
return sha2564(readFileSync7(path, "utf-8"));
|
|
10592
10868
|
}
|
|
10593
10869
|
function requiredPreviousHash(result) {
|
|
10594
10870
|
if (result.previousSha256 === null) {
|
|
@@ -10597,13 +10873,13 @@ function requiredPreviousHash(result) {
|
|
|
10597
10873
|
return result.previousSha256;
|
|
10598
10874
|
}
|
|
10599
10875
|
function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps) {
|
|
10600
|
-
const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").filter((result) =>
|
|
10601
|
-
const content =
|
|
10876
|
+
const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").filter((result) => existsSync9(result.path)).map((result) => {
|
|
10877
|
+
const content = readFileSync7(result.path, "utf-8");
|
|
10602
10878
|
return {
|
|
10603
10879
|
path: result.path,
|
|
10604
10880
|
relativePath: result.relativePath,
|
|
10605
10881
|
role: result.role,
|
|
10606
|
-
sha256:
|
|
10882
|
+
sha256: sha2564(content),
|
|
10607
10883
|
content
|
|
10608
10884
|
};
|
|
10609
10885
|
});
|
|
@@ -10654,42 +10930,42 @@ function assertSafeTargetHome(targetHome) {
|
|
|
10654
10930
|
if (!isAbsolute3(targetHome))
|
|
10655
10931
|
throw new SessionApplyError(`Session target home must be absolute: ${targetHome}`);
|
|
10656
10932
|
const normalized = resolve4(targetHome);
|
|
10657
|
-
if (normalized ===
|
|
10933
|
+
if (normalized === parse4(normalized).root) {
|
|
10658
10934
|
throw new SessionApplyError(`Session target home cannot be the filesystem root: ${targetHome}`);
|
|
10659
10935
|
}
|
|
10660
10936
|
assertNoSymlinkAncestors2(normalized);
|
|
10661
|
-
if (
|
|
10937
|
+
if (existsSync9(normalized) && lstatSync2(normalized).isSymbolicLink()) {
|
|
10662
10938
|
throw new SessionApplyError(`Session target home cannot be a symlink: ${normalized}`);
|
|
10663
10939
|
}
|
|
10664
10940
|
return normalized;
|
|
10665
10941
|
}
|
|
10666
10942
|
function assertNoSymlinkSegments2(root, target) {
|
|
10667
10943
|
assertNoSymlinkAncestors2(root);
|
|
10668
|
-
const rel =
|
|
10944
|
+
const rel = relative4(root, target);
|
|
10669
10945
|
let current = root;
|
|
10670
10946
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
10671
|
-
current =
|
|
10672
|
-
if (
|
|
10947
|
+
current = join8(current, segment);
|
|
10948
|
+
if (existsSync9(current) && lstatSync2(current).isSymbolicLink()) {
|
|
10673
10949
|
throw new SessionApplyError(`Session apply path uses a symlink: ${current}`);
|
|
10674
10950
|
}
|
|
10675
10951
|
}
|
|
10676
10952
|
}
|
|
10677
10953
|
function assertNoSymlinkAncestors2(path) {
|
|
10678
10954
|
const normalized = resolve4(path);
|
|
10679
|
-
const parsed =
|
|
10955
|
+
const parsed = parse4(normalized);
|
|
10680
10956
|
let current = parsed.root;
|
|
10681
|
-
const rel =
|
|
10957
|
+
const rel = relative4(parsed.root, normalized);
|
|
10682
10958
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
10683
|
-
current =
|
|
10684
|
-
if (!
|
|
10959
|
+
current = join8(current, segment);
|
|
10960
|
+
if (!existsSync9(current))
|
|
10685
10961
|
return;
|
|
10686
10962
|
if (lstatSync2(current).isSymbolicLink()) {
|
|
10687
10963
|
throw new SessionApplyError(`Session apply path uses a symlink ancestor: ${current}`);
|
|
10688
10964
|
}
|
|
10689
10965
|
}
|
|
10690
10966
|
}
|
|
10691
|
-
function
|
|
10692
|
-
return
|
|
10967
|
+
function sha2564(content) {
|
|
10968
|
+
return createHash4("sha256").update(content).digest("hex");
|
|
10693
10969
|
}
|
|
10694
10970
|
// src/lib/project-dashboard-standard.ts
|
|
10695
10971
|
var PROJECT_DASHBOARD_STANDARD_SLUG = "agent-managed-project-dashboard-standard";
|
|
@@ -10987,12 +11263,12 @@ async function ensureDangerousOperationGuardStandardConfig(store = resolveConfig
|
|
|
10987
11263
|
}
|
|
10988
11264
|
}
|
|
10989
11265
|
// src/lib/sync.ts
|
|
10990
|
-
import { existsSync as
|
|
10991
|
-
import { basename as basename5, extname as extname3, join as
|
|
11266
|
+
import { existsSync as existsSync11, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "fs";
|
|
11267
|
+
import { basename as basename5, extname as extname3, join as join10 } from "path";
|
|
10992
11268
|
|
|
10993
11269
|
// src/lib/sync-dir.ts
|
|
10994
|
-
import { existsSync as
|
|
10995
|
-
import { join as
|
|
11270
|
+
import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
|
|
11271
|
+
import { join as join9, relative as relative5 } from "path";
|
|
10996
11272
|
import { homedir as homedir4 } from "os";
|
|
10997
11273
|
var SKIP = [".db", ".db-shm", ".db-wal", ".log", ".lock", ".DS_Store", "node_modules", ".git"];
|
|
10998
11274
|
function shouldSkip(p) {
|
|
@@ -11001,9 +11277,9 @@ function shouldSkip(p) {
|
|
|
11001
11277
|
async function syncFromDir(dir, opts = {}) {
|
|
11002
11278
|
const store = opts.store ?? resolveConfigStore();
|
|
11003
11279
|
const absDir = expandPath(dir);
|
|
11004
|
-
if (!
|
|
11280
|
+
if (!existsSync10(absDir))
|
|
11005
11281
|
return { added: 0, updated: 0, unchanged: 0, skipped: [`Not found: ${absDir}`] };
|
|
11006
|
-
const files = opts.recursive !== false ? walkDir(absDir) : readdirSync2(absDir).map((f) =>
|
|
11282
|
+
const files = opts.recursive !== false ? walkDir(absDir) : readdirSync2(absDir).map((f) => join9(absDir, f)).filter((f) => statSync5(f).isFile());
|
|
11007
11283
|
const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
|
|
11008
11284
|
const home = homedir4();
|
|
11009
11285
|
const allConfigs = await store.listConfigs();
|
|
@@ -11013,7 +11289,7 @@ async function syncFromDir(dir, opts = {}) {
|
|
|
11013
11289
|
continue;
|
|
11014
11290
|
}
|
|
11015
11291
|
try {
|
|
11016
|
-
const content =
|
|
11292
|
+
const content = readFileSync8(file, "utf-8");
|
|
11017
11293
|
if (content.length > 500000) {
|
|
11018
11294
|
result.skipped.push(file + " (too large)");
|
|
11019
11295
|
continue;
|
|
@@ -11022,7 +11298,7 @@ async function syncFromDir(dir, opts = {}) {
|
|
|
11022
11298
|
const existing = allConfigs.find((c) => c.target_path === targetPath);
|
|
11023
11299
|
if (!existing) {
|
|
11024
11300
|
if (!opts.dryRun)
|
|
11025
|
-
await store.createConfig({ name:
|
|
11301
|
+
await store.createConfig({ name: relative5(absDir, file), category: detectCategory(file), agent: detectAgent(file), target_path: targetPath, format: detectFormat(file), content });
|
|
11026
11302
|
result.added++;
|
|
11027
11303
|
} else if (existing.content !== content) {
|
|
11028
11304
|
if (!opts.dryRun)
|
|
@@ -11063,7 +11339,7 @@ async function syncToDir(dir, opts = {}) {
|
|
|
11063
11339
|
}
|
|
11064
11340
|
function walkDir(dir, files = []) {
|
|
11065
11341
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
11066
|
-
const full =
|
|
11342
|
+
const full = join9(dir, entry.name);
|
|
11067
11343
|
if (shouldSkip(full))
|
|
11068
11344
|
continue;
|
|
11069
11345
|
if (entry.isDirectory())
|
|
@@ -11115,7 +11391,7 @@ function isGeneratedOutputTarget2(config, owners) {
|
|
|
11115
11391
|
return !!ownerIds && !ownerIds.has(config.id);
|
|
11116
11392
|
}
|
|
11117
11393
|
function hasClaudePromptSource() {
|
|
11118
|
-
return
|
|
11394
|
+
return existsSync11(expandPath("~/.claude/CLAUDE.md"));
|
|
11119
11395
|
}
|
|
11120
11396
|
function hasClaudeRuleSourceForCursorTarget(targetPath) {
|
|
11121
11397
|
const absoluteTargetPath = expandPath(targetPath);
|
|
@@ -11123,7 +11399,7 @@ function hasClaudeRuleSourceForCursorTarget(targetPath) {
|
|
|
11123
11399
|
if (!absoluteTargetPath.startsWith(`${absolutePrefix}/`) || !absoluteTargetPath.endsWith(".mdc"))
|
|
11124
11400
|
return false;
|
|
11125
11401
|
const stem = basename5(absoluteTargetPath, ".mdc");
|
|
11126
|
-
return
|
|
11402
|
+
return existsSync11(expandPath(`~/.claude/rules/${stem}.md`)) || existsSync11(expandPath(`~/.claude/rules/${stem}.mdc`));
|
|
11127
11403
|
}
|
|
11128
11404
|
function isKnownGeneratedTargetPath(targetPath) {
|
|
11129
11405
|
const normalizedTargetPath = normalizeTargetPath(targetPath);
|
|
@@ -11186,11 +11462,11 @@ async function syncProject(opts) {
|
|
|
11186
11462
|
const allConfigs = await store.listConfigs();
|
|
11187
11463
|
const machine = detectMachineContext();
|
|
11188
11464
|
for (const pf of PROJECT_CONFIG_FILES) {
|
|
11189
|
-
const abs =
|
|
11190
|
-
if (!
|
|
11465
|
+
const abs = join10(absDir, pf.file);
|
|
11466
|
+
if (!existsSync11(abs))
|
|
11191
11467
|
continue;
|
|
11192
11468
|
try {
|
|
11193
|
-
const rawContent =
|
|
11469
|
+
const rawContent = readFileSync9(abs, "utf-8");
|
|
11194
11470
|
if (rawContent.length > 500000) {
|
|
11195
11471
|
result.skipped.push(pf.file);
|
|
11196
11472
|
continue;
|
|
@@ -11219,15 +11495,15 @@ async function syncProject(opts) {
|
|
|
11219
11495
|
}
|
|
11220
11496
|
}
|
|
11221
11497
|
for (const ruleDir of [
|
|
11222
|
-
{ dir:
|
|
11223
|
-
{ dir:
|
|
11498
|
+
{ dir: join10(absDir, ".claude", "rules"), agent: "claude", namePrefix: "rules" },
|
|
11499
|
+
{ dir: join10(absDir, ".agents", "rules"), agent: "antigravity", namePrefix: "antigravity-rules" }
|
|
11224
11500
|
]) {
|
|
11225
|
-
if (!
|
|
11501
|
+
if (!existsSync11(ruleDir.dir))
|
|
11226
11502
|
continue;
|
|
11227
11503
|
const mdFiles = readdirSync3(ruleDir.dir).filter((f) => f.endsWith(".md") || f.endsWith(".mdc"));
|
|
11228
11504
|
for (const f of mdFiles) {
|
|
11229
|
-
const abs =
|
|
11230
|
-
const raw =
|
|
11505
|
+
const abs = join10(ruleDir.dir, f);
|
|
11506
|
+
const raw = readFileSync9(abs, "utf-8");
|
|
11231
11507
|
const redacted = redactContent(raw, "markdown");
|
|
11232
11508
|
const machineAware = templateizeMachineContent(redacted.content, machine);
|
|
11233
11509
|
const content = machineAware.content;
|
|
@@ -11266,20 +11542,20 @@ async function syncKnown(opts = {}) {
|
|
|
11266
11542
|
for (const known of targets) {
|
|
11267
11543
|
if (known.rulesDir) {
|
|
11268
11544
|
const absDir = expandPath(known.rulesDir);
|
|
11269
|
-
if (!
|
|
11545
|
+
if (!existsSync11(absDir)) {
|
|
11270
11546
|
result.skipped.push(known.rulesDir);
|
|
11271
11547
|
continue;
|
|
11272
11548
|
}
|
|
11273
11549
|
const extensions = known.rulesExtensions ?? [".md", ".mdc"];
|
|
11274
11550
|
const ruleFiles = readdirSync3(absDir).filter((f) => extensions.some((ext) => f.endsWith(ext)));
|
|
11275
11551
|
for (const f of ruleFiles) {
|
|
11276
|
-
const abs2 =
|
|
11552
|
+
const abs2 = join10(absDir, f);
|
|
11277
11553
|
const targetPath = abs2.replace(home, "~");
|
|
11278
11554
|
if (existingOutputOwners.has(normalizeTargetPath(targetPath)) || isKnownGeneratedTargetPath(targetPath)) {
|
|
11279
11555
|
result.skipped.push(`${targetPath} (generated output)`);
|
|
11280
11556
|
continue;
|
|
11281
11557
|
}
|
|
11282
|
-
const raw =
|
|
11558
|
+
const raw = readFileSync9(abs2, "utf-8");
|
|
11283
11559
|
const redacted = redactContent(raw, "markdown");
|
|
11284
11560
|
const machineAware = templateizeMachineContent(redacted.content, machine);
|
|
11285
11561
|
const content = machineAware.content;
|
|
@@ -11307,12 +11583,12 @@ async function syncKnown(opts = {}) {
|
|
|
11307
11583
|
continue;
|
|
11308
11584
|
}
|
|
11309
11585
|
const abs = expandPath(known.path);
|
|
11310
|
-
if (!
|
|
11586
|
+
if (!existsSync11(abs)) {
|
|
11311
11587
|
result.skipped.push(known.path);
|
|
11312
11588
|
continue;
|
|
11313
11589
|
}
|
|
11314
11590
|
try {
|
|
11315
|
-
const rawContent =
|
|
11591
|
+
const rawContent = readFileSync9(abs, "utf-8");
|
|
11316
11592
|
if (rawContent.length > 500000) {
|
|
11317
11593
|
result.skipped.push(known.path + " (too large)");
|
|
11318
11594
|
continue;
|
|
@@ -11400,9 +11676,9 @@ async function syncToDisk(opts = {}) {
|
|
|
11400
11676
|
}
|
|
11401
11677
|
function buildDiff(expectedContent, targetPath) {
|
|
11402
11678
|
const path = expandPath(targetPath);
|
|
11403
|
-
if (!
|
|
11679
|
+
if (!existsSync11(path))
|
|
11404
11680
|
return `(file not found on disk: ${path})`;
|
|
11405
|
-
const diskContent =
|
|
11681
|
+
const diskContent = readFileSync9(path, "utf-8");
|
|
11406
11682
|
if (diskContent === expectedContent)
|
|
11407
11683
|
return "(no diff \u2014 identical)";
|
|
11408
11684
|
const stored = expectedContent.split(`
|
|
@@ -11516,15 +11792,15 @@ function detectFormat(filePath) {
|
|
|
11516
11792
|
return "text";
|
|
11517
11793
|
}
|
|
11518
11794
|
// src/lib/export.ts
|
|
11519
|
-
import { existsSync as
|
|
11520
|
-
import { join as
|
|
11795
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync5, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
11796
|
+
import { join as join11, resolve as resolve5 } from "path";
|
|
11521
11797
|
import { tmpdir } from "os";
|
|
11522
11798
|
async function exportConfigs(outputPath, opts = {}) {
|
|
11523
11799
|
const store = opts.store ?? resolveConfigStore();
|
|
11524
11800
|
const configs = await store.listConfigs(opts.filter);
|
|
11525
11801
|
const absOutput = resolve5(outputPath);
|
|
11526
|
-
const tmpDir =
|
|
11527
|
-
const contentsDir =
|
|
11802
|
+
const tmpDir = join11(tmpdir(), `configs-export-${Date.now()}`);
|
|
11803
|
+
const contentsDir = join11(tmpDir, "contents");
|
|
11528
11804
|
try {
|
|
11529
11805
|
mkdirSync5(contentsDir, { recursive: true });
|
|
11530
11806
|
const manifest = {
|
|
@@ -11532,10 +11808,10 @@ async function exportConfigs(outputPath, opts = {}) {
|
|
|
11532
11808
|
exported_at: new Date().toISOString(),
|
|
11533
11809
|
configs: configs.map(({ content: _content, ...meta }) => meta)
|
|
11534
11810
|
};
|
|
11535
|
-
writeFileSync3(
|
|
11811
|
+
writeFileSync3(join11(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
|
|
11536
11812
|
for (const config of configs) {
|
|
11537
11813
|
const fileName = `${config.slug}.${config.format === "text" ? "txt" : config.format}`;
|
|
11538
|
-
writeFileSync3(
|
|
11814
|
+
writeFileSync3(join11(contentsDir, fileName), config.content, "utf-8");
|
|
11539
11815
|
}
|
|
11540
11816
|
const proc = Bun.spawn(["tar", "czf", absOutput, "-C", tmpDir, "."], {
|
|
11541
11817
|
stdout: "pipe",
|
|
@@ -11548,20 +11824,20 @@ async function exportConfigs(outputPath, opts = {}) {
|
|
|
11548
11824
|
}
|
|
11549
11825
|
return { path: absOutput, count: configs.length };
|
|
11550
11826
|
} finally {
|
|
11551
|
-
if (
|
|
11827
|
+
if (existsSync12(tmpDir)) {
|
|
11552
11828
|
rmSync3(tmpDir, { recursive: true, force: true });
|
|
11553
11829
|
}
|
|
11554
11830
|
}
|
|
11555
11831
|
}
|
|
11556
11832
|
// src/lib/import.ts
|
|
11557
|
-
import { existsSync as
|
|
11558
|
-
import { join as
|
|
11833
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync10, rmSync as rmSync4 } from "fs";
|
|
11834
|
+
import { join as join12, resolve as resolve6 } from "path";
|
|
11559
11835
|
import { tmpdir as tmpdir2 } from "os";
|
|
11560
11836
|
async function importConfigs(bundlePath, opts = {}) {
|
|
11561
11837
|
const store = opts.store ?? resolveConfigStore();
|
|
11562
11838
|
const conflict = opts.conflict ?? "skip";
|
|
11563
11839
|
const absPath = resolve6(bundlePath);
|
|
11564
|
-
const tmpDir =
|
|
11840
|
+
const tmpDir = join12(tmpdir2(), `configs-import-${Date.now()}`);
|
|
11565
11841
|
const result = { created: 0, updated: 0, skipped: 0, errors: [] };
|
|
11566
11842
|
try {
|
|
11567
11843
|
mkdirSync6(tmpDir, { recursive: true });
|
|
@@ -11574,15 +11850,15 @@ async function importConfigs(bundlePath, opts = {}) {
|
|
|
11574
11850
|
const stderr = await new Response(proc.stderr).text();
|
|
11575
11851
|
throw new Error(`tar extraction failed: ${stderr}`);
|
|
11576
11852
|
}
|
|
11577
|
-
const manifestPath =
|
|
11578
|
-
if (!
|
|
11853
|
+
const manifestPath = join12(tmpDir, "manifest.json");
|
|
11854
|
+
if (!existsSync13(manifestPath))
|
|
11579
11855
|
throw new Error("Invalid bundle: missing manifest.json");
|
|
11580
|
-
const manifest = JSON.parse(
|
|
11856
|
+
const manifest = JSON.parse(readFileSync10(manifestPath, "utf-8"));
|
|
11581
11857
|
for (const meta of manifest.configs) {
|
|
11582
11858
|
try {
|
|
11583
11859
|
const ext = meta.format === "text" ? "txt" : meta.format;
|
|
11584
|
-
const contentFile =
|
|
11585
|
-
const content =
|
|
11860
|
+
const contentFile = join12(tmpDir, "contents", `${meta.slug}.${ext}`);
|
|
11861
|
+
const content = existsSync13(contentFile) ? readFileSync10(contentFile, "utf-8") : "";
|
|
11586
11862
|
let existing = null;
|
|
11587
11863
|
try {
|
|
11588
11864
|
existing = await store.getConfig(meta.slug);
|
|
@@ -11616,16 +11892,16 @@ async function importConfigs(bundlePath, opts = {}) {
|
|
|
11616
11892
|
}
|
|
11617
11893
|
return result;
|
|
11618
11894
|
} finally {
|
|
11619
|
-
if (
|
|
11895
|
+
if (existsSync13(tmpDir)) {
|
|
11620
11896
|
rmSync4(tmpDir, { recursive: true, force: true });
|
|
11621
11897
|
}
|
|
11622
11898
|
}
|
|
11623
11899
|
}
|
|
11624
11900
|
// src/lib/package-manager-guard.ts
|
|
11625
11901
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
11626
|
-
import { existsSync as
|
|
11902
|
+
import { existsSync as existsSync14, lstatSync as lstatSync3, readdirSync as readdirSync4, readFileSync as readFileSync11 } from "fs";
|
|
11627
11903
|
import { homedir as homedir5 } from "os";
|
|
11628
|
-
import { basename as basename6, dirname as
|
|
11904
|
+
import { basename as basename6, dirname as dirname7, isAbsolute as isAbsolute4, join as join13, relative as relative6, resolve as resolve7 } from "path";
|
|
11629
11905
|
var SKIP_DIRS = new Set([
|
|
11630
11906
|
".git",
|
|
11631
11907
|
"node_modules",
|
|
@@ -11667,7 +11943,7 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
11667
11943
|
const findings = [];
|
|
11668
11944
|
let scannedFiles = 0;
|
|
11669
11945
|
for (const root of roots) {
|
|
11670
|
-
if (!
|
|
11946
|
+
if (!existsSync14(root))
|
|
11671
11947
|
continue;
|
|
11672
11948
|
const stat = lstatSync3(root);
|
|
11673
11949
|
if (stat.isFile()) {
|
|
@@ -11677,14 +11953,14 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
11677
11953
|
if (text === null)
|
|
11678
11954
|
continue;
|
|
11679
11955
|
scannedFiles++;
|
|
11680
|
-
findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root),
|
|
11956
|
+
findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root), dirname7(root)));
|
|
11681
11957
|
continue;
|
|
11682
11958
|
}
|
|
11683
11959
|
if (!stat.isDirectory())
|
|
11684
11960
|
continue;
|
|
11685
11961
|
const tracked = trackedFiles(root);
|
|
11686
11962
|
for (const file of collectRepoFiles(root)) {
|
|
11687
|
-
const rel = toPosix(
|
|
11963
|
+
const rel = toPosix(relative6(root, file));
|
|
11688
11964
|
const isTracked = tracked.has(rel);
|
|
11689
11965
|
const text = readTextFile(file);
|
|
11690
11966
|
if (text === null)
|
|
@@ -11696,8 +11972,8 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
11696
11972
|
if (options.includeHome) {
|
|
11697
11973
|
const home = homedir5();
|
|
11698
11974
|
for (const name of HOME_FILES) {
|
|
11699
|
-
const file =
|
|
11700
|
-
if (!
|
|
11975
|
+
const file = join13(home, name);
|
|
11976
|
+
if (!existsSync14(file))
|
|
11701
11977
|
continue;
|
|
11702
11978
|
const text = readTextFile(file);
|
|
11703
11979
|
if (text === null)
|
|
@@ -11721,12 +11997,12 @@ function collectRepoFiles(root) {
|
|
|
11721
11997
|
if (entry.isDirectory()) {
|
|
11722
11998
|
if (SKIP_DIRS.has(entry.name))
|
|
11723
11999
|
continue;
|
|
11724
|
-
visit(
|
|
12000
|
+
visit(join13(dir, entry.name));
|
|
11725
12001
|
continue;
|
|
11726
12002
|
}
|
|
11727
12003
|
if (!entry.isFile())
|
|
11728
12004
|
continue;
|
|
11729
|
-
const file =
|
|
12005
|
+
const file = join13(dir, entry.name);
|
|
11730
12006
|
if (shouldScanRepoFile(file))
|
|
11731
12007
|
out.push(file);
|
|
11732
12008
|
}
|
|
@@ -11764,7 +12040,7 @@ function readTextFile(file) {
|
|
|
11764
12040
|
const stat = lstatSync3(file);
|
|
11765
12041
|
if (!stat.isFile() || stat.size > 5000000)
|
|
11766
12042
|
return null;
|
|
11767
|
-
const buf =
|
|
12043
|
+
const buf = readFileSync11(file);
|
|
11768
12044
|
if (buf.includes(0))
|
|
11769
12045
|
return null;
|
|
11770
12046
|
return buf.toString("utf-8");
|
|
@@ -11964,11 +12240,11 @@ function trackedFiles(root) {
|
|
|
11964
12240
|
}
|
|
11965
12241
|
function isTrackedFile(file) {
|
|
11966
12242
|
try {
|
|
11967
|
-
const repoRoot = execFileSync2("git", ["-C",
|
|
12243
|
+
const repoRoot = execFileSync2("git", ["-C", dirname7(file), "rev-parse", "--show-toplevel"], {
|
|
11968
12244
|
encoding: "utf-8",
|
|
11969
12245
|
stdio: ["ignore", "pipe", "ignore"]
|
|
11970
12246
|
}).trim();
|
|
11971
|
-
const rel = toPosix(
|
|
12247
|
+
const rel = toPosix(relative6(repoRoot, file));
|
|
11972
12248
|
execFileSync2("git", ["-C", repoRoot, "ls-files", "--error-unmatch", "--", rel], {
|
|
11973
12249
|
stdio: ["ignore", "ignore", "ignore"]
|
|
11974
12250
|
});
|
|
@@ -11996,11 +12272,11 @@ function stripInlineComment(value) {
|
|
|
11996
12272
|
function displayPath(file, root) {
|
|
11997
12273
|
const home = homedir5();
|
|
11998
12274
|
if (root === home && (file === home || file.startsWith(home + "/")))
|
|
11999
|
-
return "~/" + toPosix(
|
|
12275
|
+
return "~/" + toPosix(relative6(home, file));
|
|
12000
12276
|
if (isAbsolute4(root) && file.startsWith(root + "/"))
|
|
12001
|
-
return toPosix(
|
|
12277
|
+
return toPosix(relative6(root, file));
|
|
12002
12278
|
if (file === home || file.startsWith(home + "/"))
|
|
12003
|
-
return "~/" + toPosix(
|
|
12279
|
+
return "~/" + toPosix(relative6(home, file));
|
|
12004
12280
|
return file;
|
|
12005
12281
|
}
|
|
12006
12282
|
function toPosix(path) {
|
|
@@ -12029,6 +12305,7 @@ export {
|
|
|
12029
12305
|
resolveProfileVariables,
|
|
12030
12306
|
resolveConfigStore,
|
|
12031
12307
|
resolveCloudConfig,
|
|
12308
|
+
resolveAgentOperatingRulesPayload,
|
|
12032
12309
|
renderTemplatePreview,
|
|
12033
12310
|
renderTemplate,
|
|
12034
12311
|
renderMachineAwareContentPreview,
|
|
@@ -12039,6 +12316,7 @@ export {
|
|
|
12039
12316
|
planProjectContext,
|
|
12040
12317
|
parseTemplateVars,
|
|
12041
12318
|
parseProjectContextBundle,
|
|
12319
|
+
parseAgentOperatingRulesVersion,
|
|
12042
12320
|
now,
|
|
12043
12321
|
normalizeOsFamily,
|
|
12044
12322
|
machineContextToVariables,
|
|
@@ -12063,6 +12341,7 @@ export {
|
|
|
12063
12341
|
currentHostname2 as currentHostname,
|
|
12064
12342
|
currentArch2 as currentArch,
|
|
12065
12343
|
computeProjectContextSourceHash,
|
|
12344
|
+
compareAgentOperatingRulesVersions,
|
|
12066
12345
|
cleanSessionPathInput,
|
|
12067
12346
|
checkSessionRenderDrift,
|
|
12068
12347
|
buildOpenCodeAgentsMd,
|
|
@@ -12118,5 +12397,7 @@ export {
|
|
|
12118
12397
|
CONFIG_FORMATS,
|
|
12119
12398
|
CONFIG_CATEGORIES,
|
|
12120
12399
|
CONFIG_AGENTS,
|
|
12121
|
-
CODEWITH_NATIVE_IMPORTS_ENV
|
|
12400
|
+
CODEWITH_NATIVE_IMPORTS_ENV,
|
|
12401
|
+
AGENT_OPERATING_RULES_SENTINEL_PATTERN,
|
|
12402
|
+
AGENT_OPERATING_RULES_SEMANTIC_POLICY_KEY
|
|
12122
12403
|
};
|