@hasna/instructions 0.5.5 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +388 -328
- package/dist/index.js +349 -294
- package/dist/lib/project-context.d.ts.map +1 -1
- package/dist/lib/session-apply.d.ts.map +1 -1
- package/dist/lib/session-render-state.d.ts +48 -0
- package/dist/lib/session-render-state.d.ts.map +1 -0
- package/dist/lib/session-render-state.test.d.ts +2 -0
- package/dist/lib/session-render-state.test.d.ts.map +1 -0
- package/dist/mcp/index.js +8 -2
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -200,6 +200,9 @@ function resolvePath(kind, options) {
|
|
|
200
200
|
function configDir(options) {
|
|
201
201
|
return resolvePath("config", options);
|
|
202
202
|
}
|
|
203
|
+
function stateDir(options) {
|
|
204
|
+
return resolvePath("state", options);
|
|
205
|
+
}
|
|
203
206
|
|
|
204
207
|
// src/lib/app-home.ts
|
|
205
208
|
var HASNA_CONFIGS_HOME_ENV = "HASNA_CONFIGS_HOME";
|
|
@@ -849,9 +852,9 @@ import { createHash as createHash7 } from "crypto";
|
|
|
849
852
|
|
|
850
853
|
// src/lib/session-render.ts
|
|
851
854
|
import { createHash as createHash6 } from "crypto";
|
|
852
|
-
import { existsSync as
|
|
853
|
-
import { homedir as
|
|
854
|
-
import { basename as basename4, dirname as
|
|
855
|
+
import { existsSync as existsSync7, readFileSync as readFileSync4, realpathSync as realpathSync2, statSync as statSync3 } from "fs";
|
|
856
|
+
import { homedir as homedir7 } from "os";
|
|
857
|
+
import { basename as basename4, dirname as dirname4, extname as extname2, isAbsolute as isAbsolute3, join as join9, parse as parse2, posix as posix2, relative as relative2, resolve as resolve8 } from "path";
|
|
855
858
|
|
|
856
859
|
// src/lib/global-agent-rules-standard.ts
|
|
857
860
|
import { createHash } from "crypto";
|
|
@@ -1164,7 +1167,7 @@ import { dlopen, FFIType } from "bun:ffi";
|
|
|
1164
1167
|
import {
|
|
1165
1168
|
closeSync,
|
|
1166
1169
|
constants,
|
|
1167
|
-
existsSync as
|
|
1170
|
+
existsSync as existsSync5,
|
|
1168
1171
|
fstatSync,
|
|
1169
1172
|
fsyncSync,
|
|
1170
1173
|
lstatSync,
|
|
@@ -1177,7 +1180,7 @@ import {
|
|
|
1177
1180
|
statSync,
|
|
1178
1181
|
writeFileSync
|
|
1179
1182
|
} from "fs";
|
|
1180
|
-
import { basename, dirname, isAbsolute, join as
|
|
1183
|
+
import { basename, dirname as dirname2, isAbsolute, join as join6, parse, relative, resolve as resolve4 } from "path";
|
|
1181
1184
|
|
|
1182
1185
|
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
|
|
1183
1186
|
var exports_external = {};
|
|
@@ -5370,6 +5373,52 @@ var SESSION_INSTRUCTION_LAYERS = [
|
|
|
5370
5373
|
"local"
|
|
5371
5374
|
];
|
|
5372
5375
|
|
|
5376
|
+
// src/lib/session-render-state.ts
|
|
5377
|
+
import { existsSync as existsSync4, readdirSync } from "fs";
|
|
5378
|
+
import { homedir as homedir4 } from "os";
|
|
5379
|
+
import { dirname, join as join5, resolve as resolve3 } from "path";
|
|
5380
|
+
var SESSION_RENDER_STATE_APP = "instructions";
|
|
5381
|
+
function homeDir2(env = process.env) {
|
|
5382
|
+
return env["HOME"] || env["USERPROFILE"] || homedir4();
|
|
5383
|
+
}
|
|
5384
|
+
function legacySnapshotDir(targetHome) {
|
|
5385
|
+
return resolve3(join5(targetHome, ".hasna", "session-render-snapshots"));
|
|
5386
|
+
}
|
|
5387
|
+
function resolverSnapshotDir(env = process.env) {
|
|
5388
|
+
const override = env.HASNA_STATE_HOME;
|
|
5389
|
+
if (typeof override === "string" && override.trim().length > 0 && !existsSync4(override)) {
|
|
5390
|
+
return stateDir({
|
|
5391
|
+
app: SESSION_RENDER_STATE_APP,
|
|
5392
|
+
env: { ...env, HASNA_STATE_HOME: undefined },
|
|
5393
|
+
home: homeDir2(env)
|
|
5394
|
+
});
|
|
5395
|
+
}
|
|
5396
|
+
return stateDir({ app: SESSION_RENDER_STATE_APP, env, home: homeDir2(env) });
|
|
5397
|
+
}
|
|
5398
|
+
function adoptResolverSnapshotDir(resolved, env = process.env) {
|
|
5399
|
+
const override = env.HASNA_STATE_HOME;
|
|
5400
|
+
if (typeof override === "string" && override.trim().length > 0 && existsSync4(override)) {
|
|
5401
|
+
return true;
|
|
5402
|
+
}
|
|
5403
|
+
if (!existsSync4(resolved))
|
|
5404
|
+
return false;
|
|
5405
|
+
return readdirSync(resolved).some((name) => name.endsWith(".json"));
|
|
5406
|
+
}
|
|
5407
|
+
function resolveSessionRenderSnapshotLocation(targetHome, env = process.env) {
|
|
5408
|
+
const resolved = resolverSnapshotDir(env);
|
|
5409
|
+
if (!adoptResolverSnapshotDir(resolved, env)) {
|
|
5410
|
+
return { dir: legacySnapshotDir(targetHome), workspaceRoot: targetHome, adopted: false };
|
|
5411
|
+
}
|
|
5412
|
+
const resolvedPath = resolve3(resolved);
|
|
5413
|
+
return { dir: resolvedPath, workspaceRoot: dirname(resolvedPath), adopted: true };
|
|
5414
|
+
}
|
|
5415
|
+
function getSessionRenderSnapshotDir(targetHome, env = process.env) {
|
|
5416
|
+
return resolveSessionRenderSnapshotLocation(targetHome, env).dir;
|
|
5417
|
+
}
|
|
5418
|
+
function sessionRenderSnapshotWorkspaceRoot(targetHome, env = process.env) {
|
|
5419
|
+
return resolveSessionRenderSnapshotLocation(targetHome, env).workspaceRoot;
|
|
5420
|
+
}
|
|
5421
|
+
|
|
5373
5422
|
// src/lib/project-context.ts
|
|
5374
5423
|
var PROJECT_CONTEXT_SCHEMA = "hasna.projects.project_context_bundle.v1";
|
|
5375
5424
|
var PROJECT_CONTEXT_SCHEMA_V2 = "hasna.projects.project_context_bundle.v2";
|
|
@@ -5673,7 +5722,7 @@ function planProjectContext(input) {
|
|
|
5673
5722
|
const inlineMarkerOverhead = nativeImports ? 0 : Buffer.byteLength(buildManagedBlock(bundle, "", `
|
|
5674
5723
|
`), "utf8");
|
|
5675
5724
|
const generated = buildCanonicalFragment(bundle, status, ageSeconds, PROJECT_CONTEXT_MAX_RENDERED_BYTES - Math.max(320, inlineMarkerOverhead), PROJECT_CONTEXT_MAX_APPROX_TOKENS - Math.max(80, Math.ceil(inlineMarkerOverhead / 4)));
|
|
5676
|
-
const previousTargetContent =
|
|
5725
|
+
const previousTargetContent = existsSync5(paths.target) ? readUtf8RegularFile(paths.target, workspaceRoot, managedObservationMaxBytes(relativePosix(workspaceRoot, paths.target))) : null;
|
|
5677
5726
|
const markerParse = parseManagedBlock(previousTargetContent ?? "", input.force === true);
|
|
5678
5727
|
if (markerParse.block && markerParse.block.id !== bundle.project.id) {
|
|
5679
5728
|
throw new ProjectContextError("MANAGED_BLOCK_CONFLICT", "managed block belongs to a different project");
|
|
@@ -5724,7 +5773,7 @@ function composeProjectContextSessionRender(input) {
|
|
|
5724
5773
|
return null;
|
|
5725
5774
|
const { runtime, workspace_root: workspaceRoot, observed_hashes: observedHashes } = guard;
|
|
5726
5775
|
const paths = runtimePaths(workspaceRoot, runtime);
|
|
5727
|
-
if (!
|
|
5776
|
+
if (!existsSync5(paths.manifest))
|
|
5728
5777
|
return null;
|
|
5729
5778
|
assertCodewithTargetIsConsumed(workspaceRoot, runtime);
|
|
5730
5779
|
const manifest = readProjectContextManifest(paths.manifest, workspaceRoot);
|
|
@@ -5753,7 +5802,7 @@ function composeProjectContextSessionRender(input) {
|
|
|
5753
5802
|
}
|
|
5754
5803
|
const fragment = readUtf8RegularFile(paths.fragment, workspaceRoot, PROJECT_CONTEXT_MAX_RENDERED_BYTES);
|
|
5755
5804
|
scanGeneratedContent(fragment);
|
|
5756
|
-
if (!
|
|
5805
|
+
if (!existsSync5(paths.target)) {
|
|
5757
5806
|
throw new ProjectContextError("MANAGED_BLOCK_CONFLICT", "project-context provider target is missing while durable context is active");
|
|
5758
5807
|
}
|
|
5759
5808
|
const currentTarget = readUtf8RegularFile(paths.target, workspaceRoot, managedObservationMaxBytes(relativePosix(workspaceRoot, paths.target)));
|
|
@@ -5764,7 +5813,7 @@ function composeProjectContextSessionRender(input) {
|
|
|
5764
5813
|
if (currentMarkers.block.id !== cache.project_id || currentMarkers.block.revision !== cache.revision || currentMarkers.block.hash !== cache.hash) {
|
|
5765
5814
|
throw new ProjectContextError("MANAGED_BLOCK_CONFLICT", "project-context provider markers differ from the durable cache");
|
|
5766
5815
|
}
|
|
5767
|
-
const plannedIndexes = input.files.filter((file) => file.role === "index" &&
|
|
5816
|
+
const plannedIndexes = input.files.filter((file) => file.role === "index" && resolve4(file.path) === paths.target);
|
|
5768
5817
|
if (plannedIndexes.length !== 1) {
|
|
5769
5818
|
throw new ProjectContextError("PROJECT_CONTEXT_MANIFEST_INVALID", "session renderer does not own the selected project-context provider target");
|
|
5770
5819
|
}
|
|
@@ -5822,7 +5871,7 @@ function withProjectContextSessionGuard(guard, action, options = {}) {
|
|
|
5822
5871
|
verify();
|
|
5823
5872
|
return action(null);
|
|
5824
5873
|
}
|
|
5825
|
-
const lockPath =
|
|
5874
|
+
const lockPath = resolve4(validated.workspace_root, ...PROJECT_CONTEXT_LOCK_PATH.split("/"));
|
|
5826
5875
|
const lock = acquireWorkspaceLock(validated.workspace_root, lockPath);
|
|
5827
5876
|
try {
|
|
5828
5877
|
verify();
|
|
@@ -5848,7 +5897,7 @@ function validateProjectContextSessionGuard(guard) {
|
|
|
5848
5897
|
if (!isRecord(observed) || typeof observed.path !== "string") {
|
|
5849
5898
|
throw new ProjectContextError("PROJECT_CONTEXT_SESSION_STALE", "session project-context guard contains malformed hash metadata");
|
|
5850
5899
|
}
|
|
5851
|
-
const path =
|
|
5900
|
+
const path = resolve4(observed.path);
|
|
5852
5901
|
if (!allowedPaths.has(path) || observedPaths.has(path)) {
|
|
5853
5902
|
throw new ProjectContextError("PROJECT_CONTEXT_SESSION_STALE", "session project-context guard contains an unexpected or duplicate path");
|
|
5854
5903
|
}
|
|
@@ -5870,7 +5919,7 @@ function validateProjectContextSessionGuard(guard) {
|
|
|
5870
5919
|
function applyProjectContext(options) {
|
|
5871
5920
|
const workspaceRoot = assertSafeWorkspaceRoot(options.workspace_root);
|
|
5872
5921
|
const now2 = options.now ?? new Date;
|
|
5873
|
-
const lockPath =
|
|
5922
|
+
const lockPath = resolve4(workspaceRoot, ...PROJECT_CONTEXT_LOCK_PATH.split("/"));
|
|
5874
5923
|
const lock = options.dry_run ? null : acquireWorkspaceLock(workspaceRoot, lockPath, options.test_hooks?.after_lock_open, options.test_hooks?.before_stale_lock_remove, options.test_hooks?.process_start_identity);
|
|
5875
5924
|
try {
|
|
5876
5925
|
const resolved = resolveBundleForApply(options, workspaceRoot, now2);
|
|
@@ -6017,7 +6066,7 @@ function resolveBundleForApply(options, workspaceRoot, now2) {
|
|
|
6017
6066
|
if (!options.expected_project_id) {
|
|
6018
6067
|
throw new ProjectContextError("PROJECT_CONTEXT_CACHE_ID_REQUIRED", "expected_project_id is required for stale-cache fallback");
|
|
6019
6068
|
}
|
|
6020
|
-
const cachePath =
|
|
6069
|
+
const cachePath = resolve4(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
|
|
6021
6070
|
const cache = readProjectContextCache(cachePath, workspaceRoot);
|
|
6022
6071
|
if (!cache)
|
|
6023
6072
|
throw new ProjectContextError("PROJECT_CONTEXT_CACHE_MISSING", "no last-known-good project context cache exists");
|
|
@@ -6219,7 +6268,7 @@ function findLegacyCodewithWorkspaceSection(workspaceRoot, runtime, content, bun
|
|
|
6219
6268
|
if (runtime !== "codewith" || !content)
|
|
6220
6269
|
return null;
|
|
6221
6270
|
const sessionManifestPath = runtimePaths(workspaceRoot, runtime).sessionManifest;
|
|
6222
|
-
if (!
|
|
6271
|
+
if (!existsSync5(sessionManifestPath))
|
|
6223
6272
|
return null;
|
|
6224
6273
|
const manifest = readSessionManifestRecord(sessionManifestPath, workspaceRoot);
|
|
6225
6274
|
if (!manifest || manifest["schema"] !== SESSION_RENDER_SCHEMA) {
|
|
@@ -6270,7 +6319,7 @@ function assertRevisionOrdering(plan, force) {
|
|
|
6270
6319
|
const manifest = readProjectContextManifest(plan.manifest_path, plan.workspace_root);
|
|
6271
6320
|
if (manifest) {
|
|
6272
6321
|
const manifestHashHasRecoveryProof = manifest.projectContext.hash === plan.bundle.hash || metadataSnapshotMatchesManifest(plan, manifest);
|
|
6273
|
-
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 &&
|
|
6322
|
+
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 && existsSync5(plan.fragment_path) && fragmentMatchesBundle(plan.fragment_path, plan.bundle, plan.workspace_root) && manifestHashHasRecoveryProof;
|
|
6274
6323
|
observations.push({
|
|
6275
6324
|
source: "manifest",
|
|
6276
6325
|
id: manifest.projectContext.projectId,
|
|
@@ -6278,7 +6327,7 @@ function assertRevisionOrdering(plan, force) {
|
|
|
6278
6327
|
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)
|
|
6279
6328
|
});
|
|
6280
6329
|
const fragmentEntry = manifest.files.find((file) => file.relativePath === PROJECT_CONTEXT_FRAGMENT_PATH);
|
|
6281
|
-
if (fragmentEntry &&
|
|
6330
|
+
if (fragmentEntry && existsSync5(plan.fragment_path)) {
|
|
6282
6331
|
const actual = currentFileHash(plan.fragment_path, plan.workspace_root);
|
|
6283
6332
|
if (actual !== fragmentEntry.sha256 && !fragmentMatchesBundle(plan.fragment_path, plan.bundle, plan.workspace_root) && !force) {
|
|
6284
6333
|
throw new ProjectContextError("MANAGED_BLOCK_CONFLICT", "canonical project-context fragment changed outside Instructions");
|
|
@@ -6373,9 +6422,9 @@ function buildManifest(plan, now2) {
|
|
|
6373
6422
|
function buildSessionCompatibilityManifest(plan, now2) {
|
|
6374
6423
|
const paths = runtimePaths(plan.workspace_root, plan.runtime);
|
|
6375
6424
|
const tool = manifestTool(plan.runtime);
|
|
6376
|
-
const targetHome = plan.runtime === "codewith" ?
|
|
6425
|
+
const targetHome = plan.runtime === "codewith" ? resolve4(plan.workspace_root, ".codewith") : plan.workspace_root;
|
|
6377
6426
|
const targetRelativePath = sessionTargetRelativePath(plan.runtime);
|
|
6378
|
-
const existing =
|
|
6427
|
+
const existing = existsSync5(paths.sessionManifest) ? readSessionManifestRecord(paths.sessionManifest, plan.workspace_root) : {
|
|
6379
6428
|
schema: SESSION_RENDER_SCHEMA,
|
|
6380
6429
|
tool,
|
|
6381
6430
|
adapterMode: plan.native_imports ? "native-imports" : "flattened-markdown",
|
|
@@ -6395,7 +6444,7 @@ function buildSessionCompatibilityManifest(plan, now2) {
|
|
|
6395
6444
|
throw new ProjectContextError("PROJECT_CONTEXT_MANIFEST_INVALID", "provider session manifest is malformed or incompatible");
|
|
6396
6445
|
}
|
|
6397
6446
|
const existingTargetHome = safeLegacyMetadataString(existing["targetHome"], null);
|
|
6398
|
-
if (existingTargetHome !== null &&
|
|
6447
|
+
if (existingTargetHome !== null && resolve4(existingTargetHome) !== targetHome) {
|
|
6399
6448
|
throw new ProjectContextError("PROJECT_CONTEXT_MANIFEST_INVALID", "provider session manifest targets a different workspace");
|
|
6400
6449
|
}
|
|
6401
6450
|
const sources = sanitizeLegacySources(existing["sources"]).filter((source) => source["id"] !== "project-context-bundle");
|
|
@@ -6681,9 +6730,9 @@ function writeMetadataSnapshot(plan, now2) {
|
|
|
6681
6730
|
const previous = readProjectContextManifest(plan.manifest_path, plan.workspace_root);
|
|
6682
6731
|
if (!previous || previous.projectContext.revision === plan.bundle.revision && previous.projectContext.hash === plan.bundle.hash)
|
|
6683
6732
|
return null;
|
|
6684
|
-
const snapshotDir =
|
|
6733
|
+
const snapshotDir = resolve4(plan.workspace_root, ...PROJECT_CONTEXT_SNAPSHOT_DIR.split("/"));
|
|
6685
6734
|
ensureSafeDirectory(snapshotDir, plan.workspace_root, 448);
|
|
6686
|
-
const snapshotPath =
|
|
6735
|
+
const snapshotPath = resolve4(snapshotDir, `${safeFilename(previous.projectContext.revision)}-${previous.projectContext.hash.slice(-12)}.json`);
|
|
6687
6736
|
const snapshot = {
|
|
6688
6737
|
schema: "hasna.configs.session-render-snapshot/v1",
|
|
6689
6738
|
kind: "project-context-metadata",
|
|
@@ -6699,9 +6748,9 @@ function writeMetadataSnapshot(plan, now2) {
|
|
|
6699
6748
|
return snapshotPath;
|
|
6700
6749
|
}
|
|
6701
6750
|
function metadataSnapshotMatchesManifest(plan, manifest) {
|
|
6702
|
-
const snapshotDir =
|
|
6703
|
-
const snapshotPath =
|
|
6704
|
-
if (!
|
|
6751
|
+
const snapshotDir = resolve4(plan.workspace_root, ...PROJECT_CONTEXT_SNAPSHOT_DIR.split("/"));
|
|
6752
|
+
const snapshotPath = resolve4(snapshotDir, `${safeFilename(manifest.projectContext.revision)}-${manifest.projectContext.hash.slice(-12)}.json`);
|
|
6753
|
+
if (!existsSync5(snapshotPath))
|
|
6705
6754
|
return false;
|
|
6706
6755
|
const record = readJsonRecord(snapshotPath, plan.workspace_root);
|
|
6707
6756
|
const result = projectContextMetadataSnapshotSchema.safeParse(record);
|
|
@@ -6749,10 +6798,11 @@ function writeProjectContextRollbackSnapshot(plan, now2, outputs) {
|
|
|
6749
6798
|
sha256: nextHash
|
|
6750
6799
|
};
|
|
6751
6800
|
});
|
|
6752
|
-
const
|
|
6753
|
-
|
|
6801
|
+
const snapshotWorkspaceRoot = sessionRenderSnapshotWorkspaceRoot(plan.workspace_root);
|
|
6802
|
+
const snapshotDir = getSessionRenderSnapshotDir(plan.workspace_root);
|
|
6803
|
+
ensureSafeDirectory(snapshotDir, snapshotWorkspaceRoot, 448);
|
|
6754
6804
|
const timestamp = now2.toISOString().replace(/[:.]/g, "-");
|
|
6755
|
-
const snapshotPath =
|
|
6805
|
+
const snapshotPath = resolve4(snapshotDir, `${timestamp}-${randomUUID2()}.json`);
|
|
6756
6806
|
const snapshot = {
|
|
6757
6807
|
schema: "hasna.configs.session-render-snapshot/v2",
|
|
6758
6808
|
createdAt: now2.toISOString(),
|
|
@@ -6766,11 +6816,11 @@ function writeProjectContextRollbackSnapshot(plan, now2, outputs) {
|
|
|
6766
6816
|
afterFiles
|
|
6767
6817
|
};
|
|
6768
6818
|
atomicWriteFile(snapshotPath, `${JSON.stringify(snapshot, null, 2)}
|
|
6769
|
-
`,
|
|
6819
|
+
`, snapshotWorkspaceRoot, 384, null);
|
|
6770
6820
|
return snapshotPath;
|
|
6771
6821
|
}
|
|
6772
6822
|
function readProjectContextManifest(path, workspaceRoot) {
|
|
6773
|
-
if (!
|
|
6823
|
+
if (!existsSync5(path))
|
|
6774
6824
|
return null;
|
|
6775
6825
|
const record = readJsonRecord(path, workspaceRoot);
|
|
6776
6826
|
const result = storedManifestObservationSchema.safeParse(record);
|
|
@@ -6785,7 +6835,7 @@ function readProjectContextManifest(path, workspaceRoot) {
|
|
|
6785
6835
|
};
|
|
6786
6836
|
}
|
|
6787
6837
|
function readProjectContextCache(path, workspaceRoot) {
|
|
6788
|
-
if (!
|
|
6838
|
+
if (!existsSync5(path))
|
|
6789
6839
|
return null;
|
|
6790
6840
|
const record = readJsonRecord(path, workspaceRoot);
|
|
6791
6841
|
const result = projectContextCacheSchema.safeParse(record);
|
|
@@ -6817,7 +6867,7 @@ function readSessionManifestRecord(path, workspaceRoot) {
|
|
|
6817
6867
|
}
|
|
6818
6868
|
}
|
|
6819
6869
|
function atomicWriteFile(path, content, workspaceRoot, defaultMode, expectedHash, afterExchange, atomicExchangeUnavailable = false, beforeInstall, portableCreateOnly = false, maxObservedBytes, allowPortableReplacement = false) {
|
|
6820
|
-
const dir =
|
|
6870
|
+
const dir = resolve4(path, "..");
|
|
6821
6871
|
ensureSafeDirectory(dir, workspaceRoot, 448);
|
|
6822
6872
|
assertNoSymlinkSegments(workspaceRoot, path);
|
|
6823
6873
|
const anchoredOps = portableCreateOnly ? null : resolveAnchoredFsOps();
|
|
@@ -6834,7 +6884,7 @@ function atomicWriteFile(path, content, workspaceRoot, defaultMode, expectedHash
|
|
|
6834
6884
|
const previous = anchoredFileObservation(directory, targetName);
|
|
6835
6885
|
const previousMode = previous?.mode ?? defaultMode;
|
|
6836
6886
|
const tempName = `.project-context-${randomUUID2()}.tmp`;
|
|
6837
|
-
const tempPath =
|
|
6887
|
+
const tempPath = join6(dir, tempName);
|
|
6838
6888
|
let fd = null;
|
|
6839
6889
|
let preserveTemp = false;
|
|
6840
6890
|
let directoryChanged = false;
|
|
@@ -6963,9 +7013,9 @@ function atomicWritePortable(path, content, workspaceRoot, defaultMode, expected
|
|
|
6963
7013
|
if (currentHash !== null) {
|
|
6964
7014
|
throw new ProjectContextHashRace(`managed path appeared before portable creation: ${relativePosix(workspaceRoot, path)}`);
|
|
6965
7015
|
}
|
|
6966
|
-
const dir =
|
|
7016
|
+
const dir = dirname2(path);
|
|
6967
7017
|
const directoryIdentity = captureManagedDirectoryIdentity(dir, workspaceRoot);
|
|
6968
|
-
const tempPath =
|
|
7018
|
+
const tempPath = join6(dir, `.project-context-${randomUUID2()}.tmp`);
|
|
6969
7019
|
let fd = null;
|
|
6970
7020
|
let tempIdentity = null;
|
|
6971
7021
|
try {
|
|
@@ -7020,9 +7070,9 @@ function atomicWritePortableReplacement(path, content, workspaceRoot, expectedHa
|
|
|
7020
7070
|
if (current.isSymbolicLink() || !current.isFile()) {
|
|
7021
7071
|
throw new ProjectContextHashRace(`managed path is not a regular file: ${relativePosix(workspaceRoot, path)}`);
|
|
7022
7072
|
}
|
|
7023
|
-
const dir =
|
|
7073
|
+
const dir = dirname2(path);
|
|
7024
7074
|
const directoryIdentity = captureManagedDirectoryIdentity(dir, workspaceRoot);
|
|
7025
|
-
const tempPath =
|
|
7075
|
+
const tempPath = join6(dir, `.project-context-${randomUUID2()}.tmp`);
|
|
7026
7076
|
const desiredHash = sha2562(content);
|
|
7027
7077
|
let fd = null;
|
|
7028
7078
|
let tempIdentity = null;
|
|
@@ -7079,7 +7129,7 @@ function portablePreparedHash(tempPath, path, workspaceRoot, maxObservedBytes, s
|
|
|
7079
7129
|
function portableFileHash(path, workspaceRoot, maxObservedBytes) {
|
|
7080
7130
|
if (maxObservedBytes === undefined)
|
|
7081
7131
|
return currentFileHash(path, workspaceRoot);
|
|
7082
|
-
if (!
|
|
7132
|
+
if (!existsSync5(path))
|
|
7083
7133
|
return null;
|
|
7084
7134
|
assertNoSymlinkSegments(workspaceRoot, path);
|
|
7085
7135
|
const stat = lstatSync(path);
|
|
@@ -7091,13 +7141,13 @@ function portableFileHash(path, workspaceRoot, maxObservedBytes) {
|
|
|
7091
7141
|
return createHash2("sha256").update(readFileSync(path)).digest("hex");
|
|
7092
7142
|
}
|
|
7093
7143
|
function writeProjectContextCoordinatedFile(input) {
|
|
7094
|
-
atomicWriteFile(
|
|
7144
|
+
atomicWriteFile(resolve4(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);
|
|
7095
7145
|
}
|
|
7096
7146
|
function removeProjectContextCoordinatedFile(input) {
|
|
7097
7147
|
const workspaceRoot = assertSafeWorkspaceRoot(input.workspace_root);
|
|
7098
|
-
const path =
|
|
7148
|
+
const path = resolve4(input.path);
|
|
7099
7149
|
assertNoSymlinkSegments(workspaceRoot, path);
|
|
7100
|
-
const dir =
|
|
7150
|
+
const dir = dirname2(path);
|
|
7101
7151
|
const anchoredOps = input.force_portable_file_ops ? null : resolveAnchoredFsOps();
|
|
7102
7152
|
if (!anchoredOps) {
|
|
7103
7153
|
if (!input.allow_portable_removal) {
|
|
@@ -7121,7 +7171,7 @@ function removeProjectContextCoordinatedFile(input) {
|
|
|
7121
7171
|
throw new ProjectContextHashRace(`managed path changed during deletion: ${relativePosix(workspaceRoot, path)}`);
|
|
7122
7172
|
}
|
|
7123
7173
|
displaced = true;
|
|
7124
|
-
input.test_hooks?.after_displace?.(
|
|
7174
|
+
input.test_hooks?.after_displace?.(join6(dir, displacedName));
|
|
7125
7175
|
const moved = anchoredFileObservation(directory, displacedName);
|
|
7126
7176
|
if (!moved || moved.dev !== observed.dev || moved.ino !== observed.ino || moved.hash !== input.expected_hash || anchoredFileObservation(directory, targetName) !== null) {
|
|
7127
7177
|
throw new ProjectContextHashRace(`managed path changed during deletion validation: ${relativePosix(workspaceRoot, path)}`);
|
|
@@ -7165,9 +7215,9 @@ function removePortableCoordinatedFile(path, workspaceRoot, expectedHash, maxObs
|
|
|
7165
7215
|
if (observed.isSymbolicLink() || !observed.isFile()) {
|
|
7166
7216
|
throw new ProjectContextHashRace(`managed path is not a regular file: ${relativePosix(workspaceRoot, path)}`);
|
|
7167
7217
|
}
|
|
7168
|
-
const dir =
|
|
7218
|
+
const dir = dirname2(path);
|
|
7169
7219
|
const directoryIdentity = captureManagedDirectoryIdentity(dir, workspaceRoot);
|
|
7170
|
-
const displacedPath =
|
|
7220
|
+
const displacedPath = join6(dir, `.project-context-delete-${randomUUID2()}.tmp`);
|
|
7171
7221
|
let displaced = false;
|
|
7172
7222
|
try {
|
|
7173
7223
|
assertManagedDirectoryStable(dir, workspaceRoot, directoryIdentity);
|
|
@@ -7178,7 +7228,7 @@ function removePortableCoordinatedFile(path, workspaceRoot, expectedHash, maxObs
|
|
|
7178
7228
|
displaced = true;
|
|
7179
7229
|
afterDisplace?.(displacedPath);
|
|
7180
7230
|
const moved = lstatSync(displacedPath);
|
|
7181
|
-
if (moved.isSymbolicLink() || !moved.isFile() || moved.dev !== observed.dev || moved.ino !== observed.ino || portableFileHash(displacedPath, workspaceRoot, maxObservedBytes) !== expectedHash ||
|
|
7231
|
+
if (moved.isSymbolicLink() || !moved.isFile() || moved.dev !== observed.dev || moved.ino !== observed.ino || portableFileHash(displacedPath, workspaceRoot, maxObservedBytes) !== expectedHash || existsSync5(path)) {
|
|
7182
7232
|
throw new ProjectContextHashRace(`managed path changed during portable deletion: ${relativePosix(workspaceRoot, path)}`);
|
|
7183
7233
|
}
|
|
7184
7234
|
rmSync2(displacedPath);
|
|
@@ -7204,7 +7254,7 @@ function restorePortableDisplacedFile(displacedPath, path, workspaceRoot, expect
|
|
|
7204
7254
|
if (displaced.isSymbolicLink() || installed.isSymbolicLink() || !displaced.isFile() || !installed.isFile() || displaced.dev !== expected.dev || displaced.ino !== expected.ino || displaced.dev !== installed.dev || displaced.ino !== installed.ino || portableFileHash(displacedPath, workspaceRoot, maxObservedBytes) !== expectedHash || portableFileHash(path, workspaceRoot, maxObservedBytes) !== expectedHash)
|
|
7205
7255
|
return false;
|
|
7206
7256
|
rmSync2(displacedPath);
|
|
7207
|
-
fsyncDirectory(
|
|
7257
|
+
fsyncDirectory(dirname2(path));
|
|
7208
7258
|
return true;
|
|
7209
7259
|
} catch {
|
|
7210
7260
|
return false;
|
|
@@ -7239,7 +7289,7 @@ function anchoredOpenExclusive(directory, name, mode) {
|
|
|
7239
7289
|
const requestedMode = mode & 4095;
|
|
7240
7290
|
let fd;
|
|
7241
7291
|
try {
|
|
7242
|
-
fd = openSync(
|
|
7292
|
+
fd = openSync(join6(directory.path, name), constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, requestedMode);
|
|
7243
7293
|
} catch {
|
|
7244
7294
|
throw new ProjectContextHashRace(`could not create prepared managed file in ${relativePosix(directory.workspaceRoot, directory.path)}`);
|
|
7245
7295
|
}
|
|
@@ -7282,7 +7332,7 @@ function anchoredFileObservation(directory, name) {
|
|
|
7282
7332
|
const stat = fstatSync(fd);
|
|
7283
7333
|
if (!stat.isFile())
|
|
7284
7334
|
throw new ProjectContextHashRace("managed output is not a regular file");
|
|
7285
|
-
const relativePath = relativePosix(directory.workspaceRoot,
|
|
7335
|
+
const relativePath = relativePosix(directory.workspaceRoot, join6(directory.path, name));
|
|
7286
7336
|
const maxBytes = directory.maxObservedBytes === undefined ? managedObservationMaxBytes(relativePath) : directory.maxObservedBytes;
|
|
7287
7337
|
if (maxBytes !== null && stat.size > maxBytes) {
|
|
7288
7338
|
throw new ProjectContextHashRace(`managed output exceeds the safe read limit: ${relativePath}`);
|
|
@@ -7308,7 +7358,7 @@ function anchoredPreparedObservation(directory, name, path, stage) {
|
|
|
7308
7358
|
return observed;
|
|
7309
7359
|
}
|
|
7310
7360
|
function captureManagedDirectoryIdentity(path, workspaceRoot) {
|
|
7311
|
-
assertNoSymlinkSegments(workspaceRoot,
|
|
7361
|
+
assertNoSymlinkSegments(workspaceRoot, join6(path, ".project-context-directory-guard"));
|
|
7312
7362
|
let stat;
|
|
7313
7363
|
try {
|
|
7314
7364
|
stat = lstatSync(path);
|
|
@@ -7321,7 +7371,7 @@ function captureManagedDirectoryIdentity(path, workspaceRoot) {
|
|
|
7321
7371
|
return { dev: stat.dev, ino: stat.ino };
|
|
7322
7372
|
}
|
|
7323
7373
|
function assertManagedDirectoryStable(path, workspaceRoot, expected) {
|
|
7324
|
-
assertNoSymlinkSegments(workspaceRoot,
|
|
7374
|
+
assertNoSymlinkSegments(workspaceRoot, join6(path, ".project-context-directory-guard"));
|
|
7325
7375
|
let current;
|
|
7326
7376
|
try {
|
|
7327
7377
|
current = lstatSync(path);
|
|
@@ -7456,10 +7506,10 @@ function resolveAnchoredFsOps() {
|
|
|
7456
7506
|
return null;
|
|
7457
7507
|
}
|
|
7458
7508
|
function acquireWorkspaceLock(workspaceRoot, lockPath, afterOpen, beforeStaleRemove, processStartIdentityLookup = processStartIdentity) {
|
|
7459
|
-
const lockDirectory =
|
|
7509
|
+
const lockDirectory = resolve4(lockPath, "..");
|
|
7460
7510
|
ensureSafeDirectory(lockDirectory, workspaceRoot, 448);
|
|
7461
7511
|
assertNoSymlinkSegments(workspaceRoot, lockPath);
|
|
7462
|
-
const tempPath =
|
|
7512
|
+
const tempPath = join6(lockDirectory, `.project-context-lock-${randomUUID2()}.tmp`);
|
|
7463
7513
|
let fd = null;
|
|
7464
7514
|
let openedIdentity = null;
|
|
7465
7515
|
let openedContentHash = null;
|
|
@@ -7493,7 +7543,7 @@ function acquireWorkspaceLock(workspaceRoot, lockPath, afterOpen, beforeStaleRem
|
|
|
7493
7543
|
linked = true;
|
|
7494
7544
|
}
|
|
7495
7545
|
fsyncDirectory(lockDirectory);
|
|
7496
|
-
if (
|
|
7546
|
+
if (existsSync5(tempPath)) {
|
|
7497
7547
|
rmSync2(tempPath);
|
|
7498
7548
|
fsyncDirectory(lockDirectory);
|
|
7499
7549
|
}
|
|
@@ -7508,7 +7558,7 @@ function acquireWorkspaceLock(workspaceRoot, lockPath, afterOpen, beforeStaleRem
|
|
|
7508
7558
|
if (linked && openedIdentity && openedContentHash) {
|
|
7509
7559
|
removeOwnedLockByInode(lockPath, openedIdentity, openedContentHash);
|
|
7510
7560
|
}
|
|
7511
|
-
if (!preserveTemp &&
|
|
7561
|
+
if (!preserveTemp && existsSync5(tempPath)) {
|
|
7512
7562
|
try {
|
|
7513
7563
|
rmSync2(tempPath);
|
|
7514
7564
|
} catch {}
|
|
@@ -7523,7 +7573,7 @@ function acquireWorkspaceLock(workspaceRoot, lockPath, afterOpen, beforeStaleRem
|
|
|
7523
7573
|
}
|
|
7524
7574
|
function removeOwnedLockByInode(lockPath, identity, expectedHash) {
|
|
7525
7575
|
try {
|
|
7526
|
-
if (!
|
|
7576
|
+
if (!existsSync5(lockPath))
|
|
7527
7577
|
return;
|
|
7528
7578
|
const current = lstatSync(lockPath);
|
|
7529
7579
|
if (current.isSymbolicLink() || current.dev !== identity.dev || current.ino !== identity.ino)
|
|
@@ -7531,7 +7581,7 @@ function removeOwnedLockByInode(lockPath, identity, expectedHash) {
|
|
|
7531
7581
|
if (expectedHash !== undefined && sha2562(readFileSync(lockPath, "utf8")) !== expectedHash)
|
|
7532
7582
|
return;
|
|
7533
7583
|
rmSync2(lockPath);
|
|
7534
|
-
fsyncDirectory(
|
|
7584
|
+
fsyncDirectory(resolve4(lockPath, ".."));
|
|
7535
7585
|
} catch {}
|
|
7536
7586
|
}
|
|
7537
7587
|
function observeStaleWorkspaceLock(lockPath, workspaceRoot, processStartIdentityLookup = processStartIdentity) {
|
|
@@ -7602,7 +7652,7 @@ function tryTakeoverStaleWorkspaceLock(candidatePath, lockPath, workspaceRoot, c
|
|
|
7602
7652
|
const candidateInstalled = !current.isSymbolicLink() && current.dev === candidateIdentity.dev && current.ino === candidateIdentity.ino && currentFileHash(lockPath, workspaceRoot) === candidateHash;
|
|
7603
7653
|
const staleDisplaced = !displaced.isSymbolicLink() && displaced.dev === stale.identity.dev && displaced.ino === stale.identity.ino && currentFileHash(candidatePath, workspaceRoot) === stale.contentHash;
|
|
7604
7654
|
if (!candidateInstalled || !staleDisplaced) {
|
|
7605
|
-
if (candidateInstalled &&
|
|
7655
|
+
if (candidateInstalled && existsSync5(candidatePath)) {
|
|
7606
7656
|
atomicExchangePaths(candidatePath, lockPath);
|
|
7607
7657
|
exchanged = false;
|
|
7608
7658
|
return false;
|
|
@@ -7610,13 +7660,13 @@ function tryTakeoverStaleWorkspaceLock(candidatePath, lockPath, workspaceRoot, c
|
|
|
7610
7660
|
throw new ProjectContextError("PROJECT_CONTEXT_LOCK_LOST", "workspace lock changed during stale-lock takeover and could not be restored safely");
|
|
7611
7661
|
}
|
|
7612
7662
|
rmSync2(candidatePath);
|
|
7613
|
-
fsyncDirectory(
|
|
7663
|
+
fsyncDirectory(resolve4(lockPath, ".."));
|
|
7614
7664
|
exchanged = false;
|
|
7615
7665
|
return true;
|
|
7616
7666
|
} catch (error) {
|
|
7617
7667
|
if (exchanged) {
|
|
7618
7668
|
try {
|
|
7619
|
-
if (currentFileHash(lockPath, workspaceRoot) === candidateHash &&
|
|
7669
|
+
if (currentFileHash(lockPath, workspaceRoot) === candidateHash && existsSync5(candidatePath)) {
|
|
7620
7670
|
atomicExchangePaths(candidatePath, lockPath);
|
|
7621
7671
|
exchanged = false;
|
|
7622
7672
|
}
|
|
@@ -7629,7 +7679,7 @@ function tryTakeoverStaleWorkspaceLock(candidatePath, lockPath, workspaceRoot, c
|
|
|
7629
7679
|
}
|
|
7630
7680
|
}
|
|
7631
7681
|
function assertWorkspaceLockHeld(lockPath, lock, workspaceRoot) {
|
|
7632
|
-
if (!
|
|
7682
|
+
if (!existsSync5(lockPath)) {
|
|
7633
7683
|
throw new ProjectContextError("PROJECT_CONTEXT_LOCK_LOST", "workspace project-context lock changed during render");
|
|
7634
7684
|
}
|
|
7635
7685
|
const current = lstatSync(lockPath);
|
|
@@ -7689,8 +7739,8 @@ function releaseWorkspaceLock(lockPath, lock, workspaceRoot) {
|
|
|
7689
7739
|
}
|
|
7690
7740
|
return;
|
|
7691
7741
|
}
|
|
7692
|
-
const lockDirectory =
|
|
7693
|
-
const releasePath =
|
|
7742
|
+
const lockDirectory = resolve4(lockPath, "..");
|
|
7743
|
+
const releasePath = join6(lockDirectory, `.project-context-release-${randomUUID2()}.tmp`);
|
|
7694
7744
|
let releaseFd = null;
|
|
7695
7745
|
let releaseIdentity = null;
|
|
7696
7746
|
let releaseHash = null;
|
|
@@ -7719,7 +7769,7 @@ function releaseWorkspaceLock(lockPath, lock, workspaceRoot) {
|
|
|
7719
7769
|
const releaseInstalled = !installed.isSymbolicLink() && installed.dev === releaseIdentity.dev && installed.ino === releaseIdentity.ino && currentFileHash(lockPath, workspaceRoot) === releaseHash;
|
|
7720
7770
|
const ownedDisplaced = !displaced.isSymbolicLink() && displaced.dev === lock.identity.dev && displaced.ino === lock.identity.ino && currentFileHash(releasePath, workspaceRoot) === lock.contentHash;
|
|
7721
7771
|
if (!releaseInstalled || !ownedDisplaced) {
|
|
7722
|
-
if (releaseInstalled &&
|
|
7772
|
+
if (releaseInstalled && existsSync5(releasePath)) {
|
|
7723
7773
|
atomicExchangePaths(releasePath, lockPath);
|
|
7724
7774
|
exchanged = false;
|
|
7725
7775
|
}
|
|
@@ -7732,7 +7782,7 @@ function releaseWorkspaceLock(lockPath, lock, workspaceRoot) {
|
|
|
7732
7782
|
} catch {
|
|
7733
7783
|
if (exchanged) {
|
|
7734
7784
|
try {
|
|
7735
|
-
if (releaseHash && currentFileHash(lockPath, workspaceRoot) === releaseHash &&
|
|
7785
|
+
if (releaseHash && currentFileHash(lockPath, workspaceRoot) === releaseHash && existsSync5(releasePath)) {
|
|
7736
7786
|
atomicExchangePaths(releasePath, lockPath);
|
|
7737
7787
|
exchanged = false;
|
|
7738
7788
|
}
|
|
@@ -7744,7 +7794,7 @@ function releaseWorkspaceLock(lockPath, lock, workspaceRoot) {
|
|
|
7744
7794
|
closeSync(releaseFd);
|
|
7745
7795
|
} catch {}
|
|
7746
7796
|
}
|
|
7747
|
-
if (!exchanged &&
|
|
7797
|
+
if (!exchanged && existsSync5(releasePath)) {
|
|
7748
7798
|
try {
|
|
7749
7799
|
rmSync2(releasePath);
|
|
7750
7800
|
} catch {}
|
|
@@ -7770,15 +7820,15 @@ function ensureSafeDirectory(path, workspaceRoot, mode) {
|
|
|
7770
7820
|
const segments = rel.split(/[\\/]+/).filter(Boolean);
|
|
7771
7821
|
let current = workspaceRoot;
|
|
7772
7822
|
for (const segment of segments) {
|
|
7773
|
-
current =
|
|
7774
|
-
if (
|
|
7823
|
+
current = join6(current, segment);
|
|
7824
|
+
if (existsSync5(current)) {
|
|
7775
7825
|
if (lstatSync(current).isSymbolicLink())
|
|
7776
7826
|
throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", `managed path uses a symlink: ${current}`);
|
|
7777
7827
|
if (!statSync(current).isDirectory())
|
|
7778
7828
|
throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", `managed path is not a directory: ${current}`);
|
|
7779
7829
|
} else {
|
|
7780
7830
|
mkdirSync2(current, { mode });
|
|
7781
|
-
fsyncDirectory(
|
|
7831
|
+
fsyncDirectory(resolve4(current, ".."));
|
|
7782
7832
|
}
|
|
7783
7833
|
}
|
|
7784
7834
|
}
|
|
@@ -7834,11 +7884,11 @@ function scanGeneratedContent(content) {
|
|
|
7834
7884
|
function runtimePaths(workspaceRoot, runtime) {
|
|
7835
7885
|
const relativeTarget = runtime === "claude" ? "CLAUDE.md" : runtime === "codewith" ? ".codewith/CODEWITH.md" : "AGENTS.md";
|
|
7836
7886
|
return {
|
|
7837
|
-
target:
|
|
7838
|
-
fragment:
|
|
7839
|
-
manifest:
|
|
7840
|
-
cache:
|
|
7841
|
-
sessionManifest: runtime === "codewith" ?
|
|
7887
|
+
target: resolve4(workspaceRoot, ...relativeTarget.split("/")),
|
|
7888
|
+
fragment: resolve4(workspaceRoot, ...PROJECT_CONTEXT_FRAGMENT_PATH.split("/")),
|
|
7889
|
+
manifest: resolve4(workspaceRoot, ...PROJECT_CONTEXT_MANIFEST_PATH.split("/")),
|
|
7890
|
+
cache: resolve4(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/")),
|
|
7891
|
+
sessionManifest: runtime === "codewith" ? resolve4(workspaceRoot, ".codewith", ".hasna", "session-render-manifest.json") : resolve4(workspaceRoot, ".hasna", "session-render-manifest.json")
|
|
7842
7892
|
};
|
|
7843
7893
|
}
|
|
7844
7894
|
function projectContextSessionGuardPaths(paths, runtime) {
|
|
@@ -7848,7 +7898,7 @@ function projectContextSessionGuardPaths(paths, runtime) {
|
|
|
7848
7898
|
paths.fragment,
|
|
7849
7899
|
paths.target,
|
|
7850
7900
|
paths.sessionManifest,
|
|
7851
|
-
...runtime === "codewith" ? [
|
|
7901
|
+
...runtime === "codewith" ? [resolve4(paths.target, "..", "CODEWITH.override.md")] : []
|
|
7852
7902
|
];
|
|
7853
7903
|
}
|
|
7854
7904
|
function sessionTargetRelativePath(runtime) {
|
|
@@ -7868,27 +7918,27 @@ function projectContextRuntimeForSessionTool(tool) {
|
|
|
7868
7918
|
return null;
|
|
7869
7919
|
}
|
|
7870
7920
|
function projectContextWorkspaceForSession(input, runtime) {
|
|
7871
|
-
const targetHome =
|
|
7921
|
+
const targetHome = resolve4(input.target_home);
|
|
7872
7922
|
if (runtime === "codewith") {
|
|
7873
|
-
const workspaceRoot = basename(targetHome) === ".codewith" ?
|
|
7923
|
+
const workspaceRoot = basename(targetHome) === ".codewith" ? dirname2(targetHome) : null;
|
|
7874
7924
|
if (!workspaceRoot)
|
|
7875
7925
|
return null;
|
|
7876
|
-
if (input.project_root &&
|
|
7926
|
+
if (input.project_root && resolve4(input.project_root) !== workspaceRoot) {
|
|
7877
7927
|
throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", "Codewith project_root must be the parent workspace of target_home");
|
|
7878
7928
|
}
|
|
7879
|
-
if (!
|
|
7929
|
+
if (!existsSync5(workspaceRoot) || !lstatSync(workspaceRoot).isDirectory())
|
|
7880
7930
|
return null;
|
|
7881
7931
|
return assertSafeWorkspaceRoot(workspaceRoot);
|
|
7882
7932
|
}
|
|
7883
|
-
if (!
|
|
7933
|
+
if (!existsSync5(targetHome) || !lstatSync(targetHome).isDirectory())
|
|
7884
7934
|
return null;
|
|
7885
7935
|
return assertSafeWorkspaceRoot(targetHome);
|
|
7886
7936
|
}
|
|
7887
7937
|
function assertCodewithTargetIsConsumed(workspaceRoot, runtime) {
|
|
7888
7938
|
if (runtime !== "codewith")
|
|
7889
7939
|
return;
|
|
7890
|
-
const override =
|
|
7891
|
-
if (!
|
|
7940
|
+
const override = resolve4(workspaceRoot, ".codewith", "CODEWITH.override.md");
|
|
7941
|
+
if (!existsSync5(override))
|
|
7892
7942
|
return;
|
|
7893
7943
|
assertNoSymlinkSegments(workspaceRoot, override);
|
|
7894
7944
|
if (!lstatSync(override).isFile())
|
|
@@ -7898,10 +7948,10 @@ function assertCodewithTargetIsConsumed(workspaceRoot, runtime) {
|
|
|
7898
7948
|
function assertSafeWorkspaceRoot(path) {
|
|
7899
7949
|
if (!isAbsolute(path))
|
|
7900
7950
|
throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", "workspace root must be absolute");
|
|
7901
|
-
const normalized =
|
|
7951
|
+
const normalized = resolve4(path);
|
|
7902
7952
|
if (normalized === parse(normalized).root)
|
|
7903
7953
|
throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", "workspace root cannot be the filesystem root");
|
|
7904
|
-
if (!
|
|
7954
|
+
if (!existsSync5(normalized) || !lstatSync(normalized).isDirectory())
|
|
7905
7955
|
throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", "workspace root must be an existing directory");
|
|
7906
7956
|
assertNoSymlinkAncestors(normalized);
|
|
7907
7957
|
if (lstatSync(normalized).isSymbolicLink())
|
|
@@ -7915,18 +7965,18 @@ function assertNoSymlinkSegments(root, target) {
|
|
|
7915
7965
|
}
|
|
7916
7966
|
let current = root;
|
|
7917
7967
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
7918
|
-
current =
|
|
7919
|
-
if (
|
|
7968
|
+
current = join6(current, segment);
|
|
7969
|
+
if (existsSync5(current) && lstatSync(current).isSymbolicLink()) {
|
|
7920
7970
|
throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", `managed path uses a symlink: ${current}`);
|
|
7921
7971
|
}
|
|
7922
7972
|
}
|
|
7923
7973
|
}
|
|
7924
7974
|
function assertNoSymlinkAncestors(path) {
|
|
7925
|
-
const normalized =
|
|
7975
|
+
const normalized = resolve4(path);
|
|
7926
7976
|
let current = parse(normalized).root;
|
|
7927
7977
|
for (const segment of relative(current, normalized).split(/[\\/]+/).filter(Boolean)) {
|
|
7928
|
-
current =
|
|
7929
|
-
if (!
|
|
7978
|
+
current = join6(current, segment);
|
|
7979
|
+
if (!existsSync5(current))
|
|
7930
7980
|
return;
|
|
7931
7981
|
if (lstatSync(current).isSymbolicLink())
|
|
7932
7982
|
throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", `workspace ancestor is a symlink: ${current}`);
|
|
@@ -7942,7 +7992,7 @@ function readUtf8RegularFile(path, workspaceRoot, maxBytes = FOREIGN_INPUT_MAX_B
|
|
|
7942
7992
|
return readFileSync(path, "utf8");
|
|
7943
7993
|
}
|
|
7944
7994
|
function currentFileHash(path, workspaceRoot) {
|
|
7945
|
-
if (!
|
|
7995
|
+
if (!existsSync5(path))
|
|
7946
7996
|
return null;
|
|
7947
7997
|
const relativePath = relativePosix(workspaceRoot, path);
|
|
7948
7998
|
return sha2562(readUtf8RegularFile(path, workspaceRoot, managedObservationMaxBytes(relativePath)));
|
|
@@ -7961,10 +8011,10 @@ function fragmentMatchesBundle(path, bundle, workspaceRoot) {
|
|
|
7961
8011
|
}
|
|
7962
8012
|
function durableSourcePath(path, workspaceRoot) {
|
|
7963
8013
|
if (!path || path.startsWith("/dev/fd/"))
|
|
7964
|
-
return
|
|
7965
|
-
const normalized = isAbsolute(path) ?
|
|
8014
|
+
return resolve4(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
|
|
8015
|
+
const normalized = isAbsolute(path) ? resolve4(path) : resolve4(workspaceRoot, path);
|
|
7966
8016
|
if (normalized.startsWith("/dev/fd/"))
|
|
7967
|
-
return
|
|
8017
|
+
return resolve4(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
|
|
7968
8018
|
return normalized;
|
|
7969
8019
|
}
|
|
7970
8020
|
function compareRevisions(incoming, previous) {
|
|
@@ -8305,7 +8355,7 @@ function compareProviderVersions(left, right) {
|
|
|
8305
8355
|
|
|
8306
8356
|
// src/lib/asset-plan.ts
|
|
8307
8357
|
import { createHash as createHash3 } from "crypto";
|
|
8308
|
-
import { isAbsolute as isAbsolute2, posix, resolve as
|
|
8358
|
+
import { isAbsolute as isAbsolute2, posix, resolve as resolve5 } from "path";
|
|
8309
8359
|
var ASSET_PLAN_SCHEMA = "hasna.instructions.asset-plan/v1";
|
|
8310
8360
|
var ASSET_CAPABILITY_SCHEMA = "hasna.instructions.asset-capability/v1";
|
|
8311
8361
|
var ASSET_BUNDLE_SCHEMA = "hasna.instructions.asset-bundle/v1";
|
|
@@ -8570,8 +8620,8 @@ function resolveAssetDestination(item, roots) {
|
|
|
8570
8620
|
if (!isAbsolute2(root))
|
|
8571
8621
|
throw new Error(`Asset ${item.assetKey} destination root must be absolute.`);
|
|
8572
8622
|
const relativePath = safeRelativePath(item.destination.relativePath);
|
|
8573
|
-
const target =
|
|
8574
|
-
const normalizedRoot =
|
|
8623
|
+
const target = resolve5(root, ...relativePath.split("/"));
|
|
8624
|
+
const normalizedRoot = resolve5(root);
|
|
8575
8625
|
if (target === normalizedRoot)
|
|
8576
8626
|
throw new Error(`Asset ${item.assetKey} destination cannot replace its root.`);
|
|
8577
8627
|
if (!target.startsWith(`${normalizedRoot}/`))
|
|
@@ -8693,8 +8743,8 @@ function deepFreeze(value) {
|
|
|
8693
8743
|
// src/lib/cursor-authority.ts
|
|
8694
8744
|
import { createHash as createHash4 } from "crypto";
|
|
8695
8745
|
import { lstatSync as lstatSync2, readFileSync as readFileSync2 } from "fs";
|
|
8696
|
-
import { homedir as
|
|
8697
|
-
import { join as
|
|
8746
|
+
import { homedir as homedir5 } from "os";
|
|
8747
|
+
import { join as join7, resolve as resolve6 } from "path";
|
|
8698
8748
|
var CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH = ".cursor/rules/hasna-global.mdc";
|
|
8699
8749
|
var CURSOR_GLOBAL_AUTHORITY_MAX_BYTES = 256 * 1024;
|
|
8700
8750
|
var CURSOR_GLOBAL_AUTHORITY_MANAGED_MARKER = "Managed by @hasna/configs cursor global authority";
|
|
@@ -8703,8 +8753,8 @@ var CURSOR_GLOBAL_AUTHORITY_FRONTMATTER_PATTERN = /^---\n[\s\S]*?\n---(?:\n|$)/;
|
|
|
8703
8753
|
function sha2564(content) {
|
|
8704
8754
|
return createHash4("sha256").update(content).digest("hex");
|
|
8705
8755
|
}
|
|
8706
|
-
function
|
|
8707
|
-
return process.env["HOME"] ||
|
|
8756
|
+
function homeDir3() {
|
|
8757
|
+
return process.env["HOME"] || homedir5();
|
|
8708
8758
|
}
|
|
8709
8759
|
function markerPayload(content, markerLine, markerIndex) {
|
|
8710
8760
|
const index = markerIndex ?? content.indexOf(markerLine);
|
|
@@ -8720,12 +8770,12 @@ function baseObservation(path) {
|
|
|
8720
8770
|
};
|
|
8721
8771
|
}
|
|
8722
8772
|
function observeCursorGlobalAuthority(options = {}) {
|
|
8723
|
-
const authorityPath =
|
|
8773
|
+
const authorityPath = resolve6(join7(options.home ?? homeDir3(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
|
|
8724
8774
|
const readFile = options.readFile ?? ((path) => readFileSync2(path, "utf8"));
|
|
8725
8775
|
return observeCursorGlobalAuthorityPath(authorityPath, readFile);
|
|
8726
8776
|
}
|
|
8727
8777
|
function observeCursorGlobalAuthorityAtPath(authorityPath) {
|
|
8728
|
-
return observeCursorGlobalAuthorityPath(
|
|
8778
|
+
return observeCursorGlobalAuthorityPath(resolve6(authorityPath), (path) => readFileSync2(path, "utf8"));
|
|
8729
8779
|
}
|
|
8730
8780
|
function observeCursorGlobalAuthorityPath(authorityPath, readFile) {
|
|
8731
8781
|
const base = baseObservation(authorityPath);
|
|
@@ -8870,7 +8920,7 @@ function observeCursorGlobalAuthorityPath(authorityPath, readFile) {
|
|
|
8870
8920
|
};
|
|
8871
8921
|
}
|
|
8872
8922
|
function isCursorGlobalAuthorityPath(path) {
|
|
8873
|
-
return
|
|
8923
|
+
return resolve6(path) === resolve6(join7(homeDir3(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
|
|
8874
8924
|
}
|
|
8875
8925
|
function stampCursorGlobalAuthorityMarker(content) {
|
|
8876
8926
|
const existing = content.match(CURSOR_GLOBAL_AUTHORITY_MARKER_PATTERN);
|
|
@@ -8920,8 +8970,8 @@ function detectCursorAuthorityConflicts(observation = observeCursorGlobalAuthori
|
|
|
8920
8970
|
// src/lib/session-authority.ts
|
|
8921
8971
|
import { createHash as createHash5 } from "crypto";
|
|
8922
8972
|
import { lstatSync as lstatSync3, readFileSync as readFileSync3, realpathSync, statSync as statSync2 } from "fs";
|
|
8923
|
-
import { homedir as
|
|
8924
|
-
import { join as
|
|
8973
|
+
import { homedir as homedir6 } from "os";
|
|
8974
|
+
import { join as join8, resolve as resolve7 } from "path";
|
|
8925
8975
|
var CLAUDE_LEGACY_AUTHORITY_RELATIVE_PATH = "AGENTS.md";
|
|
8926
8976
|
var CLAUDE_LEGACY_AUTHORITY_MAX_BYTES = 256 * 1024;
|
|
8927
8977
|
var CLAUDE_LEGACY_MARKERS = [
|
|
@@ -8933,10 +8983,10 @@ function sha2565(content) {
|
|
|
8933
8983
|
return createHash5("sha256").update(content).digest("hex");
|
|
8934
8984
|
}
|
|
8935
8985
|
function configHomeDir() {
|
|
8936
|
-
return process.env["CONFIGS_HOME"] || process.env["HOME"] ||
|
|
8986
|
+
return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir6();
|
|
8937
8987
|
}
|
|
8938
8988
|
function normalizeOwnedTargetPath(p) {
|
|
8939
|
-
const expanded = p.startsWith("~/") ?
|
|
8989
|
+
const expanded = p.startsWith("~/") ? resolve7(configHomeDir(), p.slice(2)) : resolve7(p);
|
|
8940
8990
|
try {
|
|
8941
8991
|
return realpathSync(expanded);
|
|
8942
8992
|
} catch {
|
|
@@ -8944,7 +8994,7 @@ function normalizeOwnedTargetPath(p) {
|
|
|
8944
8994
|
}
|
|
8945
8995
|
}
|
|
8946
8996
|
function detectClaudeAuthorityConflicts(targetHome, ownedAuthorities = []) {
|
|
8947
|
-
const authorityPath =
|
|
8997
|
+
const authorityPath = resolve7(join8(targetHome, CLAUDE_LEGACY_AUTHORITY_RELATIVE_PATH));
|
|
8948
8998
|
let stat;
|
|
8949
8999
|
try {
|
|
8950
9000
|
stat = lstatSync3(authorityPath);
|
|
@@ -9303,13 +9353,13 @@ function yamlQuote2(value) {
|
|
|
9303
9353
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
9304
9354
|
}
|
|
9305
9355
|
function defaultTargetHome(tool, profile, sessionId) {
|
|
9306
|
-
const home = process.env["HOME"] ||
|
|
9307
|
-
return
|
|
9356
|
+
const home = process.env["HOME"] || homedir7();
|
|
9357
|
+
return join9(home, ".hasna", "accounts", "profiles", tool, slug(profile));
|
|
9308
9358
|
}
|
|
9309
9359
|
function joinTarget(targetHome, relativePath) {
|
|
9310
9360
|
const safeTargetHome = assertSafeTargetRoot(targetHome);
|
|
9311
9361
|
const safeRelativePath2 = assertSafeRelativePath(relativePath);
|
|
9312
|
-
return
|
|
9362
|
+
return join9(safeTargetHome, ...safeRelativePath2.split("/"));
|
|
9313
9363
|
}
|
|
9314
9364
|
function makeFile(targetHome, relativePath, role, content, sourceIds) {
|
|
9315
9365
|
const safeTargetHome = assertSafeTargetRoot(targetHome);
|
|
@@ -9879,7 +9929,7 @@ function buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfi
|
|
|
9879
9929
|
...sources.flatMap((source) => source.resolvedRules.map((rule) => rule.id))
|
|
9880
9930
|
]);
|
|
9881
9931
|
const existingConfigPath = joinTarget(targetHome, adapter.configFile);
|
|
9882
|
-
const selectedConfig =
|
|
9932
|
+
const selectedConfig = existsSync7(existingConfigPath) ? readOpenCodeConfig(readFileSync4(existingConfigPath, "utf8"), existingConfigPath) : providerConfig ? readOpenCodeConfig(providerConfig.content, providerConfig.sourceId) : {};
|
|
9883
9933
|
const preservedInstructions = normalizeOpenCodeInstructions(selectedConfig["instructions"]).filter((path) => !pathIsManagedOpenCodeInstruction(path, adapter.managedDir));
|
|
9884
9934
|
const config = {
|
|
9885
9935
|
...selectedConfig,
|
|
@@ -10068,7 +10118,7 @@ function adapterFor(input) {
|
|
|
10068
10118
|
return gatedNativeImports ? CODEWITH_NATIVE_ADAPTER : CODEWITH_FLATTENED_ADAPTER;
|
|
10069
10119
|
}
|
|
10070
10120
|
function getHomeDir() {
|
|
10071
|
-
return process.env["CONFIGS_HOME"] || process.env["HOME"] ||
|
|
10121
|
+
return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir7();
|
|
10072
10122
|
}
|
|
10073
10123
|
function cleanSessionPathInput(path) {
|
|
10074
10124
|
const trimmed = path.trim();
|
|
@@ -10083,16 +10133,16 @@ function resolveSessionPath(path) {
|
|
|
10083
10133
|
throw new Error("Session render path cannot be empty.");
|
|
10084
10134
|
const home = getHomeDir();
|
|
10085
10135
|
if (cleaned === "~")
|
|
10086
|
-
return
|
|
10136
|
+
return resolve8(home);
|
|
10087
10137
|
if (cleaned.startsWith("~/"))
|
|
10088
|
-
return
|
|
10138
|
+
return resolve8(home, cleaned.slice(2));
|
|
10089
10139
|
if (cleaned === "{{HOME}}" || cleaned === "${HOME}")
|
|
10090
|
-
return
|
|
10140
|
+
return resolve8(home);
|
|
10091
10141
|
if (cleaned.startsWith("{{HOME}}/"))
|
|
10092
|
-
return
|
|
10142
|
+
return resolve8(home, cleaned.slice("{{HOME}}/".length));
|
|
10093
10143
|
if (cleaned.startsWith("${HOME}/"))
|
|
10094
|
-
return
|
|
10095
|
-
return
|
|
10144
|
+
return resolve8(home, cleaned.slice("${HOME}/".length));
|
|
10145
|
+
return resolve8(cleaned);
|
|
10096
10146
|
}
|
|
10097
10147
|
function assertSafeRelativePath(relativePath) {
|
|
10098
10148
|
if (!relativePath.trim())
|
|
@@ -10108,7 +10158,7 @@ function assertSafeRelativePath(relativePath) {
|
|
|
10108
10158
|
function assertSafeTargetRoot(targetHome) {
|
|
10109
10159
|
if (!isAbsolute3(targetHome))
|
|
10110
10160
|
throw new Error(`Session render target must be an absolute path: ${targetHome}`);
|
|
10111
|
-
const normalized =
|
|
10161
|
+
const normalized = resolve8(targetHome);
|
|
10112
10162
|
if (normalized === parse2(normalized).root) {
|
|
10113
10163
|
throw new Error(`Session render target cannot be the filesystem root: ${targetHome}`);
|
|
10114
10164
|
}
|
|
@@ -10344,7 +10394,7 @@ function planSessionRender(input) {
|
|
|
10344
10394
|
sourceId: input.providerConfig.sourceId,
|
|
10345
10395
|
selectedPayloadSha256: sha2566(input.providerConfig.content),
|
|
10346
10396
|
renderedPayloadSha256: files.find((file) => file.relativePath === adapter.configFile)?.sha256 ?? sha2566(input.providerConfig.content),
|
|
10347
|
-
selected: !
|
|
10397
|
+
selected: !existsSync7(joinTarget(targetHome, adapter.configFile))
|
|
10348
10398
|
}
|
|
10349
10399
|
} : {},
|
|
10350
10400
|
...projectContext ? {
|
|
@@ -10706,7 +10756,7 @@ function layerFromIdentityKind(kind, exportShape) {
|
|
|
10706
10756
|
function contentFromIdentitySourcePaths(sourcePaths, exportPath, sourceId) {
|
|
10707
10757
|
if (sourcePaths.length === 0 || !exportPath)
|
|
10708
10758
|
return;
|
|
10709
|
-
const baseDir2 =
|
|
10759
|
+
const baseDir2 = dirname4(resolveSessionPath(exportPath));
|
|
10710
10760
|
const contents = [];
|
|
10711
10761
|
for (const sourcePath of sourcePaths) {
|
|
10712
10762
|
const content = readIdentitySourcePath(sourcePath, baseDir2, sourceId);
|
|
@@ -10724,7 +10774,7 @@ ${item.content.trimEnd()}`).join(`
|
|
|
10724
10774
|
}
|
|
10725
10775
|
function readIdentitySourcePath(sourcePath, baseDir2, sourceId) {
|
|
10726
10776
|
const resolvedPath = resolveIdentitySourcePath(sourcePath.path, baseDir2, sourceId);
|
|
10727
|
-
if (!
|
|
10777
|
+
if (!existsSync7(resolvedPath)) {
|
|
10728
10778
|
if (sourcePath.required) {
|
|
10729
10779
|
throw new Error(`Required identity instruction source path not found for ${sourceId}: ${sourcePath.path}`);
|
|
10730
10780
|
}
|
|
@@ -10747,8 +10797,8 @@ function resolveIdentitySourcePath(path, baseDir2, sourceId) {
|
|
|
10747
10797
|
throw new Error(`Identity instruction source path cannot be empty for ${sourceId}.`);
|
|
10748
10798
|
if (cleaned.includes("\\"))
|
|
10749
10799
|
throw new Error(`Identity instruction source path must use POSIX separators for ${sourceId}: ${path}`);
|
|
10750
|
-
const resolvedPath = isAbsolute3(cleaned) ?
|
|
10751
|
-
if (!pathIsInside(resolvedPath,
|
|
10800
|
+
const resolvedPath = isAbsolute3(cleaned) ? resolve8(cleaned) : resolve8(baseDir2, cleaned);
|
|
10801
|
+
if (!pathIsInside(resolvedPath, resolve8(baseDir2))) {
|
|
10752
10802
|
throw new Error(`Identity instruction source path escapes export directory for ${sourceId}: ${path}`);
|
|
10753
10803
|
}
|
|
10754
10804
|
return resolvedPath;
|
|
@@ -12155,16 +12205,16 @@ function resolveConfigStore(env = process.env) {
|
|
|
12155
12205
|
return cloud ? new CloudConfigStore(cloud) : new LocalConfigStore;
|
|
12156
12206
|
}
|
|
12157
12207
|
// src/status.ts
|
|
12158
|
-
import { existsSync as
|
|
12208
|
+
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
|
|
12159
12209
|
|
|
12160
12210
|
// src/lib/apply.ts
|
|
12161
|
-
import { existsSync as
|
|
12162
|
-
import { basename as basename5, dirname as
|
|
12163
|
-
import { homedir as
|
|
12211
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync6, realpathSync as realpathSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
12212
|
+
import { basename as basename5, dirname as dirname6, join as join11, resolve as resolve9 } from "path";
|
|
12213
|
+
import { homedir as homedir8 } from "os";
|
|
12164
12214
|
|
|
12165
12215
|
// src/lib/session-render-ownership.ts
|
|
12166
|
-
import { existsSync as
|
|
12167
|
-
import { dirname as
|
|
12216
|
+
import { existsSync as existsSync8, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
12217
|
+
import { dirname as dirname5, join as join10, parse as parse3, relative as relative3, sep } from "path";
|
|
12168
12218
|
var MANIFEST_ANCESTOR_LIMIT = 24;
|
|
12169
12219
|
var MANAGED_PATH_SEGMENTS = SESSION_RENDER_EXCLUSIVE_MANAGED_PATHS.map((managedPath) => managedPath.split("/").filter(Boolean));
|
|
12170
12220
|
var manifestCache = new Map;
|
|
@@ -12186,7 +12236,7 @@ function pathIsSessionRenderManagedDir(absolutePath2) {
|
|
|
12186
12236
|
function readManifestRelativePaths(manifestPath) {
|
|
12187
12237
|
let stats;
|
|
12188
12238
|
try {
|
|
12189
|
-
if (!
|
|
12239
|
+
if (!existsSync8(manifestPath))
|
|
12190
12240
|
return null;
|
|
12191
12241
|
stats = statSync4(manifestPath);
|
|
12192
12242
|
} catch {
|
|
@@ -12213,16 +12263,16 @@ function readManifestRelativePaths(manifestPath) {
|
|
|
12213
12263
|
}
|
|
12214
12264
|
function sessionRenderManifestClaimsPath(absolutePath2) {
|
|
12215
12265
|
const root = parse3(absolutePath2).root;
|
|
12216
|
-
let home =
|
|
12266
|
+
let home = dirname5(absolutePath2);
|
|
12217
12267
|
for (let depth = 0;depth < MANIFEST_ANCESTOR_LIMIT; depth += 1) {
|
|
12218
|
-
const manifestPath =
|
|
12268
|
+
const manifestPath = join10(home, ...SESSION_RENDER_MANIFEST_RELATIVE_PATH.split("/"));
|
|
12219
12269
|
const relativePaths = readManifestRelativePaths(manifestPath);
|
|
12220
12270
|
if (relativePaths) {
|
|
12221
12271
|
const claimed = relative3(home, absolutePath2).split(sep).join("/");
|
|
12222
12272
|
if (relativePaths.has(claimed))
|
|
12223
12273
|
return true;
|
|
12224
12274
|
}
|
|
12225
|
-
const parent =
|
|
12275
|
+
const parent = dirname5(home);
|
|
12226
12276
|
if (parent === home || home === root)
|
|
12227
12277
|
break;
|
|
12228
12278
|
home = parent;
|
|
@@ -12235,13 +12285,13 @@ function sessionRenderOwnsPath(absolutePath2) {
|
|
|
12235
12285
|
|
|
12236
12286
|
// src/lib/apply.ts
|
|
12237
12287
|
function getConfigHome() {
|
|
12238
|
-
return process.env["CONFIGS_HOME"] || process.env["HOME"] ||
|
|
12288
|
+
return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir8();
|
|
12239
12289
|
}
|
|
12240
12290
|
function expandPath(p) {
|
|
12241
12291
|
if (p.startsWith("~/")) {
|
|
12242
|
-
return
|
|
12292
|
+
return resolve9(getConfigHome(), p.slice(2));
|
|
12243
12293
|
}
|
|
12244
|
-
return
|
|
12294
|
+
return resolve9(p);
|
|
12245
12295
|
}
|
|
12246
12296
|
function normalizeTargetPath(p) {
|
|
12247
12297
|
const expanded = expandPath(p);
|
|
@@ -12251,14 +12301,14 @@ function normalizeTargetPath(p) {
|
|
|
12251
12301
|
let current = expanded;
|
|
12252
12302
|
const missingSegments = [];
|
|
12253
12303
|
while (true) {
|
|
12254
|
-
if (
|
|
12304
|
+
if (existsSync9(current)) {
|
|
12255
12305
|
try {
|
|
12256
|
-
return
|
|
12306
|
+
return resolve9(realpathSync3(current), ...missingSegments);
|
|
12257
12307
|
} catch {
|
|
12258
12308
|
return expanded;
|
|
12259
12309
|
}
|
|
12260
12310
|
}
|
|
12261
|
-
const parent =
|
|
12311
|
+
const parent = dirname6(current);
|
|
12262
12312
|
const name = basename5(current);
|
|
12263
12313
|
if (parent === current)
|
|
12264
12314
|
return expanded;
|
|
@@ -12282,11 +12332,11 @@ async function writeConfigResult(config, targetPath, content, opts, meta = {}) {
|
|
|
12282
12332
|
}
|
|
12283
12333
|
const path = expandPath(renderedTargetPath);
|
|
12284
12334
|
const renderedForTarget = isCursorGlobalAuthorityPath(path) ? stampCursorGlobalAuthorityMarker(renderedContent) : renderedContent;
|
|
12285
|
-
const previousContent =
|
|
12335
|
+
const previousContent = existsSync9(path) ? readFileSync6(path, "utf-8") : null;
|
|
12286
12336
|
const changed = previousContent !== renderedForTarget;
|
|
12287
12337
|
if (!opts.dryRun) {
|
|
12288
|
-
const dir =
|
|
12289
|
-
if (!
|
|
12338
|
+
const dir = dirname6(path);
|
|
12339
|
+
if (!existsSync9(dir)) {
|
|
12290
12340
|
mkdirSync3(dir, { recursive: true });
|
|
12291
12341
|
}
|
|
12292
12342
|
if (previousContent !== null && changed) {
|
|
@@ -12320,7 +12370,7 @@ function wouldDestroyACredential(targetPath, renderedContent, format) {
|
|
|
12320
12370
|
let current;
|
|
12321
12371
|
try {
|
|
12322
12372
|
const path = expandPath(targetPath);
|
|
12323
|
-
if (!
|
|
12373
|
+
if (!existsSync9(path))
|
|
12324
12374
|
return [];
|
|
12325
12375
|
current = readFileSync6(path, "utf-8");
|
|
12326
12376
|
} catch {
|
|
@@ -12646,31 +12696,31 @@ function sessionRendererOwnsCanonicalTarget(normalized, opts) {
|
|
|
12646
12696
|
getConfigHome(),
|
|
12647
12697
|
opts.vars?.["HOME_DIR"]
|
|
12648
12698
|
].filter((home) => typeof home === "string" && home.length > 0));
|
|
12649
|
-
if ([...homes].some((home) => SESSION_RENDER_OWNED_CONFIG_TARGETS.some((relativePath) => normalized === normalizeTargetPath(
|
|
12699
|
+
if ([...homes].some((home) => SESSION_RENDER_OWNED_CONFIG_TARGETS.some((relativePath) => normalized === normalizeTargetPath(join11(home, ...relativePath.split("/"))))))
|
|
12650
12700
|
return true;
|
|
12651
12701
|
return sessionRenderOwnsPath(normalized);
|
|
12652
12702
|
}
|
|
12653
12703
|
|
|
12654
12704
|
// src/lib/package-version.ts
|
|
12655
|
-
import { existsSync as
|
|
12656
|
-
import { dirname as
|
|
12705
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
|
|
12706
|
+
import { dirname as dirname7, join as join12 } from "path";
|
|
12657
12707
|
import { fileURLToPath } from "url";
|
|
12658
12708
|
var cached = null;
|
|
12659
12709
|
function getPackageVersion() {
|
|
12660
12710
|
if (cached)
|
|
12661
12711
|
return cached;
|
|
12662
12712
|
try {
|
|
12663
|
-
let dir =
|
|
12713
|
+
let dir = dirname7(fileURLToPath(import.meta.url));
|
|
12664
12714
|
for (let i = 0;i < 8; i++) {
|
|
12665
|
-
const pkgPath =
|
|
12666
|
-
if (
|
|
12715
|
+
const pkgPath = join12(dir, "package.json");
|
|
12716
|
+
if (existsSync10(pkgPath)) {
|
|
12667
12717
|
const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
|
|
12668
12718
|
if (pkg.name === "@hasna/instructions" && pkg.version) {
|
|
12669
12719
|
cached = pkg.version;
|
|
12670
12720
|
return cached;
|
|
12671
12721
|
}
|
|
12672
12722
|
}
|
|
12673
|
-
const parent =
|
|
12723
|
+
const parent = dirname7(dir);
|
|
12674
12724
|
if (parent === dir)
|
|
12675
12725
|
break;
|
|
12676
12726
|
dir = parent;
|
|
@@ -12684,7 +12734,7 @@ function getPackageVersion() {
|
|
|
12684
12734
|
import { createHash as createHash8 } from "crypto";
|
|
12685
12735
|
import { spawnSync } from "child_process";
|
|
12686
12736
|
import {
|
|
12687
|
-
existsSync as
|
|
12737
|
+
existsSync as existsSync11,
|
|
12688
12738
|
lstatSync as lstatSync4,
|
|
12689
12739
|
mkdirSync as mkdirSync4,
|
|
12690
12740
|
readFileSync as readFileSync8,
|
|
@@ -12692,8 +12742,8 @@ import {
|
|
|
12692
12742
|
rmSync as rmSync3,
|
|
12693
12743
|
writeFileSync as writeFileSync3
|
|
12694
12744
|
} from "fs";
|
|
12695
|
-
import { homedir as
|
|
12696
|
-
import { dirname as
|
|
12745
|
+
import { homedir as homedir9 } from "os";
|
|
12746
|
+
import { dirname as dirname8, join as join13, parse as parse4, relative as relative4, resolve as resolve10 } from "path";
|
|
12697
12747
|
var INBOX_CONVERSATIONS_MINIMUM_VERSION = "0.5.28";
|
|
12698
12748
|
var INBOX_SKILL_MARKERS = [
|
|
12699
12749
|
[".claude", "skills", "inbox", "SKILL.md"],
|
|
@@ -12714,13 +12764,13 @@ function lstatOrNull(path) {
|
|
|
12714
12764
|
}
|
|
12715
12765
|
}
|
|
12716
12766
|
function findSymlinkedAncestor(path) {
|
|
12717
|
-
const normalized =
|
|
12767
|
+
const normalized = resolve10(path);
|
|
12718
12768
|
const parsed = parse4(normalized);
|
|
12719
12769
|
let current = parsed.root;
|
|
12720
12770
|
const rel = relative4(parsed.root, normalized);
|
|
12721
12771
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
12722
|
-
current =
|
|
12723
|
-
if (!
|
|
12772
|
+
current = join13(current, segment);
|
|
12773
|
+
if (!existsSync11(current))
|
|
12724
12774
|
return null;
|
|
12725
12775
|
if (lstatSync4(current).isSymbolicLink())
|
|
12726
12776
|
return current;
|
|
@@ -12737,11 +12787,11 @@ function packagedInboxSkillPath(explicitPath) {
|
|
|
12737
12787
|
if (explicitPath)
|
|
12738
12788
|
return explicitPath;
|
|
12739
12789
|
const candidates = [
|
|
12740
|
-
|
|
12741
|
-
|
|
12742
|
-
|
|
12790
|
+
join13(import.meta.dir, "..", "..", "assets", "skills", "inbox", "SKILL.md"),
|
|
12791
|
+
join13(import.meta.dir, "..", "assets", "skills", "inbox", "SKILL.md"),
|
|
12792
|
+
join13(process.cwd(), "assets", "skills", "inbox", "SKILL.md")
|
|
12743
12793
|
];
|
|
12744
|
-
const found = candidates.find((candidate) =>
|
|
12794
|
+
const found = candidates.find((candidate) => existsSync11(candidate));
|
|
12745
12795
|
if (!found) {
|
|
12746
12796
|
throw new Error(`packaged inbox skill contract is missing (checked ${candidates.length} package-relative locations)`);
|
|
12747
12797
|
}
|
|
@@ -12790,8 +12840,8 @@ function compareVersions(left, right) {
|
|
|
12790
12840
|
}
|
|
12791
12841
|
return 0;
|
|
12792
12842
|
}
|
|
12793
|
-
function inspectSkillMarkers(
|
|
12794
|
-
return INBOX_SKILL_MARKERS.map((parts) =>
|
|
12843
|
+
function inspectSkillMarkers(homeDir4) {
|
|
12844
|
+
return INBOX_SKILL_MARKERS.map((parts) => join13(homeDir4, ...parts)).map((path) => {
|
|
12795
12845
|
const stat = lstatOrNull(path);
|
|
12796
12846
|
if (!stat)
|
|
12797
12847
|
return null;
|
|
@@ -12807,9 +12857,9 @@ function inspectSkillMarkers(homeDir3) {
|
|
|
12807
12857
|
}).filter((snapshot) => snapshot !== null);
|
|
12808
12858
|
}
|
|
12809
12859
|
function inspectInbox(options) {
|
|
12810
|
-
const
|
|
12860
|
+
const homeDir4 = options.homeDir ?? homedir9();
|
|
12811
12861
|
const runtimeCommand = options.conversationsCommand ?? "conversations";
|
|
12812
|
-
const snapshots = inspectSkillMarkers(
|
|
12862
|
+
const snapshots = inspectSkillMarkers(homeDir4);
|
|
12813
12863
|
const skillPresent = snapshots.length > 0;
|
|
12814
12864
|
let canonicalContent = null;
|
|
12815
12865
|
let canonicalSha256 = null;
|
|
@@ -12835,7 +12885,7 @@ function inspectInbox(options) {
|
|
|
12835
12885
|
let reason = "skill not installed";
|
|
12836
12886
|
if (skillPresent) {
|
|
12837
12887
|
const nonRegular = snapshots.some((snapshot) => !snapshot.regular);
|
|
12838
|
-
const symlinkAncestor = snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(
|
|
12888
|
+
const symlinkAncestor = snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname8(path))).find((found) => found !== null);
|
|
12839
12889
|
if (nonRegular)
|
|
12840
12890
|
reason = "managed skill target is not a regular file";
|
|
12841
12891
|
else if (symlinkAncestor)
|
|
@@ -12922,10 +12972,10 @@ function cleanup(path) {
|
|
|
12922
12972
|
rmSync3(path, { force: true });
|
|
12923
12973
|
}
|
|
12924
12974
|
function writeAtomic(path, content, mode) {
|
|
12925
|
-
assertNoSymlinkAncestors2(
|
|
12975
|
+
assertNoSymlinkAncestors2(dirname8(path));
|
|
12926
12976
|
const tempPath = `${path}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
12927
12977
|
try {
|
|
12928
|
-
mkdirSync4(
|
|
12978
|
+
mkdirSync4(dirname8(path), { recursive: true, mode: 493 });
|
|
12929
12979
|
writeFileSync3(tempPath, content, { mode, flag: "wx" });
|
|
12930
12980
|
renameSync2(tempPath, path);
|
|
12931
12981
|
} finally {
|
|
@@ -13001,7 +13051,7 @@ async function reconcileManagedSkillRuntimes(options = {}) {
|
|
|
13001
13051
|
dry_run: dryRun
|
|
13002
13052
|
};
|
|
13003
13053
|
}
|
|
13004
|
-
const symlinkedAncestor = before.snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(
|
|
13054
|
+
const symlinkedAncestor = before.snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname8(path))).find((found) => found !== null);
|
|
13005
13055
|
if (symlinkedAncestor) {
|
|
13006
13056
|
return {
|
|
13007
13057
|
runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
|
|
@@ -13123,7 +13173,7 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
|
|
|
13123
13173
|
continue;
|
|
13124
13174
|
knownTargets += 1;
|
|
13125
13175
|
const targetPath = expandPath(config.target_path);
|
|
13126
|
-
if (!
|
|
13176
|
+
if (!existsSync12(targetPath)) {
|
|
13127
13177
|
missingTargets += 1;
|
|
13128
13178
|
continue;
|
|
13129
13179
|
}
|
|
@@ -13219,8 +13269,8 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
|
|
|
13219
13269
|
}
|
|
13220
13270
|
// src/lib/provider-context.ts
|
|
13221
13271
|
import { createHash as createHash9 } from "crypto";
|
|
13222
|
-
import { existsSync as
|
|
13223
|
-
import { join as
|
|
13272
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
|
|
13273
|
+
import { join as join14 } from "path";
|
|
13224
13274
|
var PROVIDER_CONTEXT_DIR = ".hasna/provider-context";
|
|
13225
13275
|
var PROVIDER_CONTEXT_MANIFEST = "manifest.json";
|
|
13226
13276
|
var PROVIDER_CONTEXT_SCHEMA = "hasna.instructions.provider-context/v1";
|
|
@@ -13365,17 +13415,17 @@ function resolveAndRenderProviderContext(opts) {
|
|
|
13365
13415
|
const recordedEndpoint = originAccepted ? `${opts.origin.host}${opts.origin.pathPrefix || ""}` : null;
|
|
13366
13416
|
const reason = entry === null && opts.rawEndpoint ? originAccepted ? `endpoint "${recordedEndpoint}" is not in the provider-context registry; using the invariant fragment` : "endpoint rejected (embedded credentials or unparseable); using the invariant fragment" : null;
|
|
13367
13417
|
const content = renderProviderFragment(entry);
|
|
13368
|
-
const dir =
|
|
13369
|
-
if (!
|
|
13418
|
+
const dir = join14(opts.homeDir, PROVIDER_CONTEXT_DIR);
|
|
13419
|
+
if (!existsSync13(dir))
|
|
13370
13420
|
mkdirSync5(dir, { recursive: true });
|
|
13371
13421
|
const filename = `${entry ? entry.key : "invariant"}.md`;
|
|
13372
|
-
const fragmentPath2 =
|
|
13422
|
+
const fragmentPath2 = join14(dir, filename);
|
|
13373
13423
|
const fragmentSha256 = sha2569(content);
|
|
13374
13424
|
writeFileSync4(fragmentPath2, content, "utf8");
|
|
13375
|
-
const manifestPath =
|
|
13425
|
+
const manifestPath = join14(dir, PROVIDER_CONTEXT_MANIFEST);
|
|
13376
13426
|
let manifest = { schema: PROVIDER_CONTEXT_SCHEMA, fragments: {} };
|
|
13377
13427
|
try {
|
|
13378
|
-
if (
|
|
13428
|
+
if (existsSync13(manifestPath)) {
|
|
13379
13429
|
const parsed = JSON.parse(readFileSync10(manifestPath, "utf8"));
|
|
13380
13430
|
if (parsed && typeof parsed === "object")
|
|
13381
13431
|
manifest = parsed;
|
|
@@ -13487,9 +13537,9 @@ var PG_MIGRATIONS = [
|
|
|
13487
13537
|
];
|
|
13488
13538
|
// src/lib/station-profile.ts
|
|
13489
13539
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
13490
|
-
import { existsSync as
|
|
13491
|
-
import { arch as osArch, homedir as
|
|
13492
|
-
import { dirname as
|
|
13540
|
+
import { existsSync as existsSync14, lstatSync as lstatSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync11, readdirSync as readdirSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
13541
|
+
import { arch as osArch, homedir as homedir10, hostname as osHostname, platform as osPlatform, userInfo as osUserInfo } from "os";
|
|
13542
|
+
import { dirname as dirname9, join as join15 } from "path";
|
|
13493
13543
|
var STATION_PROFILE_CACHE_FILENAME = "station-profile.md";
|
|
13494
13544
|
var STATION_PROFILE_SOURCE_ID = "station-profile";
|
|
13495
13545
|
var STATION_PROFILE_LAYER = "machine";
|
|
@@ -13499,21 +13549,21 @@ var STATION_PROFILE_FULL_NAMES_MAX = 6;
|
|
|
13499
13549
|
var STATION_PROFILE_PRIMARY_SCOPE = "@hasna";
|
|
13500
13550
|
var MACHINES_MANIFEST_PATH_ENV = "HASNA_MACHINES_MANIFEST_PATH";
|
|
13501
13551
|
var BUN_INSTALL_ENV = "BUN_INSTALL";
|
|
13502
|
-
function
|
|
13503
|
-
return env["HOME"] || env["USERPROFILE"] ||
|
|
13552
|
+
function homeDir4(env = process.env) {
|
|
13553
|
+
return env["HOME"] || env["USERPROFILE"] || homedir10();
|
|
13504
13554
|
}
|
|
13505
13555
|
function getStationProfileCachePath(env = process.env) {
|
|
13506
|
-
return
|
|
13556
|
+
return join15(getRawStoreRoot(env), STATION_PROFILE_CACHE_FILENAME);
|
|
13507
13557
|
}
|
|
13508
13558
|
function getMachinesManifestPath(env = process.env) {
|
|
13509
|
-
return env[MACHINES_MANIFEST_PATH_ENV] ||
|
|
13559
|
+
return env[MACHINES_MANIFEST_PATH_ENV] || join15(homeDir4(env), ".hasna", "machines", "machines.json");
|
|
13510
13560
|
}
|
|
13511
13561
|
function getBunGlobalModulesDir(env = process.env) {
|
|
13512
|
-
return
|
|
13562
|
+
return join15(env[BUN_INSTALL_ENV] || join15(homeDir4(env), ".bun"), "install", "global", "node_modules");
|
|
13513
13563
|
}
|
|
13514
13564
|
function readMachinesManifest(path) {
|
|
13515
13565
|
try {
|
|
13516
|
-
if (!
|
|
13566
|
+
if (!existsSync14(path))
|
|
13517
13567
|
return null;
|
|
13518
13568
|
const parsed = JSON.parse(readFileSync11(path, "utf8"));
|
|
13519
13569
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
@@ -13569,9 +13619,9 @@ function probeMachineStatus(machineId) {
|
|
|
13569
13619
|
function resolveStationProfileMachine(env = process.env, options = {}) {
|
|
13570
13620
|
const hostname2 = osHostname();
|
|
13571
13621
|
const record = findLocalManifestMachine(readMachinesManifest(getMachinesManifestPath(env)), hostname2);
|
|
13572
|
-
const home =
|
|
13622
|
+
const home = homeDir4(env);
|
|
13573
13623
|
const platform = stringField(record, "platform") ?? osPlatform();
|
|
13574
|
-
const workspacePath = stringField(record, "workspacePath") ??
|
|
13624
|
+
const workspacePath = stringField(record, "workspacePath") ?? join15(home, platform === "darwin" ? "Workspace" : "workspace");
|
|
13575
13625
|
const machine = {
|
|
13576
13626
|
id: stringField(record, "id") ?? hostname2,
|
|
13577
13627
|
hostname: stringField(record, "hostname") ?? hostname2,
|
|
@@ -13588,9 +13638,9 @@ function resolveStationProfileMachine(env = process.env, options = {}) {
|
|
|
13588
13638
|
return machine;
|
|
13589
13639
|
}
|
|
13590
13640
|
function scopedPackageNames(modulesDir, scope) {
|
|
13591
|
-
const scopeDir =
|
|
13641
|
+
const scopeDir = join15(modulesDir, scope);
|
|
13592
13642
|
try {
|
|
13593
|
-
if (!
|
|
13643
|
+
if (!existsSync14(scopeDir))
|
|
13594
13644
|
return null;
|
|
13595
13645
|
return readdirNames(scopeDir).sort();
|
|
13596
13646
|
} catch {
|
|
@@ -13598,9 +13648,9 @@ function scopedPackageNames(modulesDir, scope) {
|
|
|
13598
13648
|
}
|
|
13599
13649
|
}
|
|
13600
13650
|
function readdirNames(dir) {
|
|
13601
|
-
return
|
|
13651
|
+
return readdirSync2(dir).filter((name) => {
|
|
13602
13652
|
try {
|
|
13603
|
-
return lstatSync5(
|
|
13653
|
+
return lstatSync5(join15(dir, name)).isDirectory();
|
|
13604
13654
|
} catch {
|
|
13605
13655
|
return false;
|
|
13606
13656
|
}
|
|
@@ -13610,7 +13660,7 @@ function resolveStationProfilePackages(env = process.env) {
|
|
|
13610
13660
|
const modulesDir = getBunGlobalModulesDir(env);
|
|
13611
13661
|
let scopeDirs;
|
|
13612
13662
|
try {
|
|
13613
|
-
if (!
|
|
13663
|
+
if (!existsSync14(modulesDir))
|
|
13614
13664
|
return null;
|
|
13615
13665
|
scopeDirs = readdirNames(modulesDir).filter((name) => name.startsWith("@") && name.toLowerCase().includes("hasna"));
|
|
13616
13666
|
} catch {
|
|
@@ -13685,9 +13735,9 @@ function refreshStationProfile(options = {}) {
|
|
|
13685
13735
|
const path = getStationProfileCachePath(env);
|
|
13686
13736
|
const generatedAt = new Date().toISOString();
|
|
13687
13737
|
if (!options.dryRun) {
|
|
13688
|
-
const existing =
|
|
13738
|
+
const existing = existsSync14(path) ? readFileSync11(path, "utf8") : null;
|
|
13689
13739
|
if (existing !== content) {
|
|
13690
|
-
mkdirSync6(
|
|
13740
|
+
mkdirSync6(dirname9(path), { recursive: true });
|
|
13691
13741
|
writeFileSync5(path, content, "utf8");
|
|
13692
13742
|
}
|
|
13693
13743
|
}
|
|
@@ -13704,7 +13754,7 @@ function refreshStationProfile(options = {}) {
|
|
|
13704
13754
|
function readStationProfile(env = process.env) {
|
|
13705
13755
|
const path = getStationProfileCachePath(env);
|
|
13706
13756
|
try {
|
|
13707
|
-
if (!
|
|
13757
|
+
if (!existsSync14(path))
|
|
13708
13758
|
return null;
|
|
13709
13759
|
return readFileSync11(path, "utf8");
|
|
13710
13760
|
} catch {
|
|
@@ -13728,14 +13778,14 @@ function stationProfileSource(env = process.env) {
|
|
|
13728
13778
|
// src/lib/session-apply.ts
|
|
13729
13779
|
import { createHash as createHash10, randomUUID as randomUUID4 } from "crypto";
|
|
13730
13780
|
import {
|
|
13731
|
-
existsSync as
|
|
13781
|
+
existsSync as existsSync15,
|
|
13732
13782
|
lstatSync as lstatSync6,
|
|
13733
13783
|
mkdirSync as mkdirSync7,
|
|
13734
13784
|
readFileSync as readFileSync12,
|
|
13735
|
-
readdirSync as
|
|
13785
|
+
readdirSync as readdirSync3,
|
|
13736
13786
|
statSync as statSync5
|
|
13737
13787
|
} from "fs";
|
|
13738
|
-
import { dirname as
|
|
13788
|
+
import { dirname as dirname10, isAbsolute as isAbsolute4, join as join16, parse as parse5, relative as relative5, resolve as resolve11 } from "path";
|
|
13739
13789
|
class SessionApplyError extends Error {
|
|
13740
13790
|
constructor(message) {
|
|
13741
13791
|
super(message);
|
|
@@ -13866,13 +13916,13 @@ function assertClaudeAuthorityStillClear(plan, targetHome, ownedClaudeAuthoritie
|
|
|
13866
13916
|
throw new SessionApplyError(`Claude authority changed after planning; refusing to apply: ${summary}`);
|
|
13867
13917
|
}
|
|
13868
13918
|
function ensureSessionTargetHome(targetHome) {
|
|
13869
|
-
if (!
|
|
13919
|
+
if (!existsSync15(targetHome))
|
|
13870
13920
|
mkdirSync7(targetHome, { recursive: true, mode: 448 });
|
|
13871
13921
|
assertSafeTargetHome(targetHome);
|
|
13872
13922
|
}
|
|
13873
13923
|
function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
13874
13924
|
const safeTargetHome = assertSafeTargetHome(targetHome);
|
|
13875
|
-
const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(relative5(safeTargetHome,
|
|
13925
|
+
const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(relative5(safeTargetHome, resolve11(manifestPath)), safeTargetHome) : resolve11(safeTargetHome, ".hasna", "session-render-manifest.json");
|
|
13876
13926
|
const checkedAt = new Date().toISOString();
|
|
13877
13927
|
const previousManifest = readPreviousManifest(resolvedManifestPath);
|
|
13878
13928
|
if (!previousManifest) {
|
|
@@ -13889,7 +13939,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
|
13889
13939
|
const drifted = [];
|
|
13890
13940
|
for (const file of previousManifest.files) {
|
|
13891
13941
|
const target = resolveManifestRelativePath(file.relativePath, safeTargetHome);
|
|
13892
|
-
if (!
|
|
13942
|
+
if (!existsSync15(target)) {
|
|
13893
13943
|
missing.push({
|
|
13894
13944
|
path: target,
|
|
13895
13945
|
relativePath: file.relativePath,
|
|
@@ -13922,12 +13972,17 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
|
13922
13972
|
function restoreSessionRenderSnapshot(snapshotPath, options = {}) {
|
|
13923
13973
|
const snapshot = readSessionRenderSnapshot(snapshotPath);
|
|
13924
13974
|
const targetHome = assertSafeTargetHome(snapshot.targetHome);
|
|
13925
|
-
const resolvedSnapshotPath =
|
|
13926
|
-
const
|
|
13927
|
-
|
|
13928
|
-
|
|
13975
|
+
const resolvedSnapshotPath = resolve11(snapshotPath);
|
|
13976
|
+
const snapshotDir = getSessionRenderSnapshotDir(targetHome);
|
|
13977
|
+
const snapshotDirRelative = relative5(snapshotDir, resolvedSnapshotPath);
|
|
13978
|
+
const insideSnapshotDir = snapshotDirRelative !== ".." && !snapshotDirRelative.startsWith("../") && !isAbsolute4(snapshotDirRelative);
|
|
13979
|
+
if (!insideSnapshotDir) {
|
|
13980
|
+
const snapshotRelativePath = relative5(targetHome, resolvedSnapshotPath);
|
|
13981
|
+
if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") || isAbsolute4(snapshotRelativePath)) {
|
|
13982
|
+
throw new SessionApplyError("Session snapshot must be stored inside its session-render snapshot location.");
|
|
13983
|
+
}
|
|
13929
13984
|
}
|
|
13930
|
-
assertNoSymlinkSegments2(targetHome, resolvedSnapshotPath);
|
|
13985
|
+
assertNoSymlinkSegments2(sessionRenderSnapshotWorkspaceRoot(targetHome), resolvedSnapshotPath);
|
|
13931
13986
|
const guard = observeProjectContextSessionGuard({
|
|
13932
13987
|
tool: snapshot.tool,
|
|
13933
13988
|
target_home: targetHome,
|
|
@@ -14040,8 +14095,8 @@ function requiredRestoreHash(file) {
|
|
|
14040
14095
|
return file.previousSha256;
|
|
14041
14096
|
}
|
|
14042
14097
|
function readSessionRenderSnapshot(snapshotPath) {
|
|
14043
|
-
const resolved =
|
|
14044
|
-
if (!
|
|
14098
|
+
const resolved = resolve11(snapshotPath);
|
|
14099
|
+
if (!existsSync15(resolved))
|
|
14045
14100
|
throw new SessionApplyError(`Session snapshot not found: ${snapshotPath}`);
|
|
14046
14101
|
const stat = lstatSync6(resolved);
|
|
14047
14102
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
@@ -14121,7 +14176,7 @@ function readSessionRenderSnapshot(snapshotPath) {
|
|
|
14121
14176
|
}
|
|
14122
14177
|
function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previousManifestFiles, targetHome, snapshotPath) {
|
|
14123
14178
|
assertNoNewerSessionSnapshot(snapshotPath, snapshot.createdAt, targetHome);
|
|
14124
|
-
const manifestPath =
|
|
14179
|
+
const manifestPath = resolve11(snapshot.manifestPath);
|
|
14125
14180
|
const manifestRelativePath = relative5(targetHome, manifestPath).replaceAll("\\", "/");
|
|
14126
14181
|
resolveSnapshotFilePath(manifestRelativePath, snapshot.manifestPath, targetHome);
|
|
14127
14182
|
const manifestSha256 = currentSessionFileHash(manifestPath, targetHome);
|
|
@@ -14138,7 +14193,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
|
|
|
14138
14193
|
throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is invalid: ${snapshotPath}`);
|
|
14139
14194
|
}
|
|
14140
14195
|
const appliedManifest = parsedManifest;
|
|
14141
|
-
if (appliedManifest.schema !== SESSION_RENDER_SCHEMA || appliedManifest.tool !== snapshot.tool || appliedManifest.profile !== snapshot.profile || typeof appliedManifest.targetHome !== "string" ||
|
|
14196
|
+
if (appliedManifest.schema !== SESSION_RENDER_SCHEMA || appliedManifest.tool !== snapshot.tool || appliedManifest.profile !== snapshot.profile || typeof appliedManifest.targetHome !== "string" || resolve11(appliedManifest.targetHome) !== targetHome || appliedManifest.targetKind !== "session-home" && appliedManifest.targetKind !== "project-root" || !Array.isArray(appliedManifest.files)) {
|
|
14142
14197
|
throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest does not match its snapshot: ${snapshotPath}`);
|
|
14143
14198
|
}
|
|
14144
14199
|
const afterFiles = [];
|
|
@@ -14222,9 +14277,9 @@ function assertNoNewerSessionSnapshot(snapshotPath, createdAt, targetHome) {
|
|
|
14222
14277
|
if (!Number.isFinite(createdAtMs)) {
|
|
14223
14278
|
throw new SessionApplyError(`Pre-rollback legacy v1 snapshot has an invalid creation time: ${snapshotPath}`);
|
|
14224
14279
|
}
|
|
14225
|
-
for (const entry of
|
|
14226
|
-
const candidatePath =
|
|
14227
|
-
if (candidatePath ===
|
|
14280
|
+
for (const entry of readdirSync3(dirname10(snapshotPath))) {
|
|
14281
|
+
const candidatePath = resolve11(dirname10(snapshotPath), entry);
|
|
14282
|
+
if (candidatePath === resolve11(snapshotPath) || !entry.endsWith(".json"))
|
|
14228
14283
|
continue;
|
|
14229
14284
|
const candidateStat = lstatSync6(candidatePath);
|
|
14230
14285
|
if (candidateStat.isSymbolicLink() || !candidateStat.isFile() || candidateStat.size > 32 * 1024 * 1024)
|
|
@@ -14232,7 +14287,7 @@ function assertNoNewerSessionSnapshot(snapshotPath, createdAt, targetHome) {
|
|
|
14232
14287
|
try {
|
|
14233
14288
|
const candidate = JSON.parse(readFileSync12(candidatePath, "utf8"));
|
|
14234
14289
|
const candidateCreatedAtMs = typeof candidate.createdAt === "string" ? Date.parse(candidate.createdAt) : Number.NaN;
|
|
14235
|
-
if ((candidate.schema === "hasna.configs.session-render-snapshot/v1" || candidate.schema === "hasna.configs.session-render-snapshot/v2") && typeof candidate.targetHome === "string" &&
|
|
14290
|
+
if ((candidate.schema === "hasna.configs.session-render-snapshot/v1" || candidate.schema === "hasna.configs.session-render-snapshot/v2") && typeof candidate.targetHome === "string" && resolve11(candidate.targetHome) === targetHome && Number.isFinite(candidateCreatedAtMs) && candidateCreatedAtMs >= createdAtMs) {
|
|
14236
14291
|
throw new SessionApplyError(`Cannot restore pre-rollback legacy v1 snapshot after a newer session snapshot exists: ${candidatePath}`);
|
|
14237
14292
|
}
|
|
14238
14293
|
} catch (error) {
|
|
@@ -14301,14 +14356,14 @@ function inferLegacySnapshotAction(file, previousFiles, previousManifestFiles, p
|
|
|
14301
14356
|
}
|
|
14302
14357
|
function resolveSnapshotFilePath(relativePath, recordedPath, targetHome) {
|
|
14303
14358
|
const path = resolveManifestRelativePath(relativePath, targetHome);
|
|
14304
|
-
if (
|
|
14359
|
+
if (resolve11(recordedPath) !== path) {
|
|
14305
14360
|
throw new SessionApplyError(`Session snapshot file path mismatch for ${relativePath}`);
|
|
14306
14361
|
}
|
|
14307
14362
|
return path;
|
|
14308
14363
|
}
|
|
14309
14364
|
function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options) {
|
|
14310
14365
|
const target = resolvePlannedFilePath(plan, file, targetHome);
|
|
14311
|
-
const previousContent =
|
|
14366
|
+
const previousContent = existsSync15(target) ? readFileSync12(target, "utf-8") : null;
|
|
14312
14367
|
const previousSha256 = previousContent === null ? null : sha25610(previousContent);
|
|
14313
14368
|
const previouslyManaged = isPreviouslyManaged(file, previousSha256, previousHashes, previousManifest);
|
|
14314
14369
|
const changed = previousContent !== file.content;
|
|
@@ -14407,7 +14462,7 @@ function planStaleFileResults(plan, targetHome, previousManifest, currentRelativ
|
|
|
14407
14462
|
}
|
|
14408
14463
|
function planStaleFileResult(file, targetHome, options) {
|
|
14409
14464
|
const target = resolveManifestRelativePath(file.relativePath, targetHome);
|
|
14410
|
-
if (!
|
|
14465
|
+
if (!existsSync15(target))
|
|
14411
14466
|
return null;
|
|
14412
14467
|
const previousContent = readFileSync12(target, "utf-8");
|
|
14413
14468
|
const previousSha256 = sha25610(previousContent);
|
|
@@ -14454,19 +14509,19 @@ function isPreviouslyManaged(file, previousSha256, previousHashes, previousManif
|
|
|
14454
14509
|
return previousHashes.get(file.relativePath) === previousSha256;
|
|
14455
14510
|
}
|
|
14456
14511
|
function resolvePlannedFilePath(plan, file, targetHome) {
|
|
14457
|
-
const target =
|
|
14512
|
+
const target = resolve11(targetHome, ...file.relativePath.split("/"));
|
|
14458
14513
|
const rel = relative5(targetHome, target);
|
|
14459
14514
|
if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute4(rel)) {
|
|
14460
14515
|
throw new SessionApplyError(`Session file escapes target home: ${file.relativePath}`);
|
|
14461
14516
|
}
|
|
14462
|
-
if (
|
|
14517
|
+
if (resolve11(file.path) !== target) {
|
|
14463
14518
|
throw new SessionApplyError(`Session file path mismatch for ${file.relativePath}: ${file.path}`);
|
|
14464
14519
|
}
|
|
14465
14520
|
assertNoSymlinkSegments2(targetHome, target);
|
|
14466
14521
|
return target;
|
|
14467
14522
|
}
|
|
14468
14523
|
function resolveManifestRelativePath(relativePath, targetHome) {
|
|
14469
|
-
const target =
|
|
14524
|
+
const target = resolve11(targetHome, ...relativePath.split(/[\\/]+/));
|
|
14470
14525
|
const rel = relative5(targetHome, target);
|
|
14471
14526
|
if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute4(rel)) {
|
|
14472
14527
|
throw new SessionApplyError(`Session manifest file escapes target home: ${relativePath}`);
|
|
@@ -14475,7 +14530,7 @@ function resolveManifestRelativePath(relativePath, targetHome) {
|
|
|
14475
14530
|
return target;
|
|
14476
14531
|
}
|
|
14477
14532
|
function readPreviousManifest(path) {
|
|
14478
|
-
if (!
|
|
14533
|
+
if (!existsSync15(path))
|
|
14479
14534
|
return null;
|
|
14480
14535
|
try {
|
|
14481
14536
|
const parsed = JSON.parse(readFileSync12(path, "utf-8"));
|
|
@@ -14517,7 +14572,7 @@ function assertExpectedSessionFileHash(path, targetHome, expectedHash) {
|
|
|
14517
14572
|
}
|
|
14518
14573
|
function currentSessionFileHash(path, targetHome) {
|
|
14519
14574
|
assertNoSymlinkSegments2(targetHome, path);
|
|
14520
|
-
if (!
|
|
14575
|
+
if (!existsSync15(path))
|
|
14521
14576
|
return null;
|
|
14522
14577
|
const stat = lstatSync6(path);
|
|
14523
14578
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
@@ -14532,7 +14587,7 @@ function requiredPreviousHash(result) {
|
|
|
14532
14587
|
return result.previousSha256;
|
|
14533
14588
|
}
|
|
14534
14589
|
function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps) {
|
|
14535
|
-
const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").filter((result) =>
|
|
14590
|
+
const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").filter((result) => existsSync15(result.path)).map((result) => {
|
|
14536
14591
|
const content = readFileSync12(result.path, "utf-8");
|
|
14537
14592
|
return {
|
|
14538
14593
|
path: result.path,
|
|
@@ -14551,7 +14606,7 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
|
|
|
14551
14606
|
};
|
|
14552
14607
|
}
|
|
14553
14608
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
14554
|
-
const snapshotPath =
|
|
14609
|
+
const snapshotPath = join16(getSessionRenderSnapshotDir(targetHome), `${timestamp}-${randomUUID4()}.json`);
|
|
14555
14610
|
const afterFiles = results.map((result) => {
|
|
14556
14611
|
if (result.action === "conflict") {
|
|
14557
14612
|
throw new SessionApplyError(`Cannot snapshot unresolved conflict: ${result.relativePath}`);
|
|
@@ -14581,7 +14636,7 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
|
|
|
14581
14636
|
path: snapshotPath,
|
|
14582
14637
|
content: `${JSON.stringify(snapshot, null, 2)}
|
|
14583
14638
|
`,
|
|
14584
|
-
workspace_root: targetHome,
|
|
14639
|
+
workspace_root: sessionRenderSnapshotWorkspaceRoot(targetHome),
|
|
14585
14640
|
default_mode: 384,
|
|
14586
14641
|
expected_hash: null,
|
|
14587
14642
|
max_observed_bytes: null,
|
|
@@ -14599,12 +14654,12 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
|
|
|
14599
14654
|
function assertSafeTargetHome(targetHome) {
|
|
14600
14655
|
if (!isAbsolute4(targetHome))
|
|
14601
14656
|
throw new SessionApplyError(`Session target home must be absolute: ${targetHome}`);
|
|
14602
|
-
const normalized =
|
|
14657
|
+
const normalized = resolve11(targetHome);
|
|
14603
14658
|
if (normalized === parse5(normalized).root) {
|
|
14604
14659
|
throw new SessionApplyError(`Session target home cannot be the filesystem root: ${targetHome}`);
|
|
14605
14660
|
}
|
|
14606
14661
|
assertNoSymlinkAncestors3(normalized);
|
|
14607
|
-
if (
|
|
14662
|
+
if (existsSync15(normalized) && lstatSync6(normalized).isSymbolicLink()) {
|
|
14608
14663
|
throw new SessionApplyError(`Session target home cannot be a symlink: ${normalized}`);
|
|
14609
14664
|
}
|
|
14610
14665
|
return normalized;
|
|
@@ -14614,20 +14669,20 @@ function assertNoSymlinkSegments2(root, target) {
|
|
|
14614
14669
|
const rel = relative5(root, target);
|
|
14615
14670
|
let current = root;
|
|
14616
14671
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
14617
|
-
current =
|
|
14618
|
-
if (
|
|
14672
|
+
current = join16(current, segment);
|
|
14673
|
+
if (existsSync15(current) && lstatSync6(current).isSymbolicLink()) {
|
|
14619
14674
|
throw new SessionApplyError(`Session apply path uses a symlink: ${current}`);
|
|
14620
14675
|
}
|
|
14621
14676
|
}
|
|
14622
14677
|
}
|
|
14623
14678
|
function assertNoSymlinkAncestors3(path) {
|
|
14624
|
-
const normalized =
|
|
14679
|
+
const normalized = resolve11(path);
|
|
14625
14680
|
const parsed = parse5(normalized);
|
|
14626
14681
|
let current = parsed.root;
|
|
14627
14682
|
const rel = relative5(parsed.root, normalized);
|
|
14628
14683
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
14629
|
-
current =
|
|
14630
|
-
if (!
|
|
14684
|
+
current = join16(current, segment);
|
|
14685
|
+
if (!existsSync15(current))
|
|
14631
14686
|
return;
|
|
14632
14687
|
if (lstatSync6(current).isSymbolicLink()) {
|
|
14633
14688
|
throw new SessionApplyError(`Session apply path uses a symlink ancestor: ${current}`);
|
|
@@ -14938,13 +14993,13 @@ async function ensureDangerousOperationGuardStandardConfig(store = resolveConfig
|
|
|
14938
14993
|
}
|
|
14939
14994
|
}
|
|
14940
14995
|
// src/lib/sync.ts
|
|
14941
|
-
import { existsSync as
|
|
14942
|
-
import { basename as basename6, extname as extname3, join as
|
|
14996
|
+
import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync14 } from "fs";
|
|
14997
|
+
import { basename as basename6, extname as extname3, join as join18 } from "path";
|
|
14943
14998
|
|
|
14944
14999
|
// src/lib/sync-dir.ts
|
|
14945
|
-
import { existsSync as
|
|
14946
|
-
import { join as
|
|
14947
|
-
import { homedir as
|
|
15000
|
+
import { existsSync as existsSync16, readdirSync as readdirSync4, readFileSync as readFileSync13, statSync as statSync6 } from "fs";
|
|
15001
|
+
import { join as join17, relative as relative6 } from "path";
|
|
15002
|
+
import { homedir as homedir11 } from "os";
|
|
14948
15003
|
var SKIP = [".db", ".db-shm", ".db-wal", ".log", ".lock", ".DS_Store", "node_modules", ".git"];
|
|
14949
15004
|
function shouldSkip(p) {
|
|
14950
15005
|
return SKIP.some((s) => p.includes(s));
|
|
@@ -14952,11 +15007,11 @@ function shouldSkip(p) {
|
|
|
14952
15007
|
async function syncFromDir(dir, opts = {}) {
|
|
14953
15008
|
const store = opts.store ?? resolveConfigStore();
|
|
14954
15009
|
const absDir = expandPath(dir);
|
|
14955
|
-
if (!
|
|
15010
|
+
if (!existsSync16(absDir))
|
|
14956
15011
|
return { added: 0, updated: 0, unchanged: 0, skipped: [`Not found: ${absDir}`] };
|
|
14957
|
-
const files = opts.recursive !== false ? walkDir(absDir) :
|
|
15012
|
+
const files = opts.recursive !== false ? walkDir(absDir) : readdirSync4(absDir).map((f) => join17(absDir, f)).filter((f) => statSync6(f).isFile());
|
|
14958
15013
|
const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
|
|
14959
|
-
const home =
|
|
15014
|
+
const home = homedir11();
|
|
14960
15015
|
const allConfigs = await store.listConfigs();
|
|
14961
15016
|
for (const file of files) {
|
|
14962
15017
|
if (shouldSkip(file)) {
|
|
@@ -14991,7 +15046,7 @@ async function syncFromDir(dir, opts = {}) {
|
|
|
14991
15046
|
}
|
|
14992
15047
|
async function syncToDir(dir, opts = {}) {
|
|
14993
15048
|
const store = opts.store ?? resolveConfigStore();
|
|
14994
|
-
const home =
|
|
15049
|
+
const home = homedir11();
|
|
14995
15050
|
const absDir = expandPath(dir);
|
|
14996
15051
|
const normalized = dir.startsWith("~/") ? dir : absDir.replace(home, "~");
|
|
14997
15052
|
const configs = (await store.listConfigs()).filter((c) => c.target_path && (c.target_path.startsWith(normalized) || c.target_path.startsWith(absDir)));
|
|
@@ -15014,8 +15069,8 @@ async function syncToDir(dir, opts = {}) {
|
|
|
15014
15069
|
return result;
|
|
15015
15070
|
}
|
|
15016
15071
|
function walkDir(dir, files = []) {
|
|
15017
|
-
for (const entry of
|
|
15018
|
-
const full =
|
|
15072
|
+
for (const entry of readdirSync4(dir, { withFileTypes: true })) {
|
|
15073
|
+
const full = join17(dir, entry.name);
|
|
15019
15074
|
if (shouldSkip(full))
|
|
15020
15075
|
continue;
|
|
15021
15076
|
if (entry.isDirectory())
|
|
@@ -15076,7 +15131,7 @@ function isGeneratedOutputTarget2(config, owners) {
|
|
|
15076
15131
|
return !!ownerIds && !ownerIds.has(config.id);
|
|
15077
15132
|
}
|
|
15078
15133
|
function hasClaudePromptSource() {
|
|
15079
|
-
return
|
|
15134
|
+
return existsSync17(expandPath("~/.claude/CLAUDE.md"));
|
|
15080
15135
|
}
|
|
15081
15136
|
function hasClaudeRuleSourceForCursorTarget(targetPath) {
|
|
15082
15137
|
const absoluteTargetPath = expandPath(targetPath);
|
|
@@ -15084,7 +15139,7 @@ function hasClaudeRuleSourceForCursorTarget(targetPath) {
|
|
|
15084
15139
|
if (!absoluteTargetPath.startsWith(`${absolutePrefix}/`) || !absoluteTargetPath.endsWith(".mdc"))
|
|
15085
15140
|
return false;
|
|
15086
15141
|
const stem = basename6(absoluteTargetPath, ".mdc");
|
|
15087
|
-
return
|
|
15142
|
+
return existsSync17(expandPath(`~/.claude/rules/${stem}.md`)) || existsSync17(expandPath(`~/.claude/rules/${stem}.mdc`));
|
|
15088
15143
|
}
|
|
15089
15144
|
function isKnownGeneratedTargetPath(targetPath) {
|
|
15090
15145
|
const normalizedTargetPath = normalizeTargetPath(targetPath);
|
|
@@ -15149,8 +15204,8 @@ async function syncProject(opts) {
|
|
|
15149
15204
|
const allConfigs = await store.listConfigs();
|
|
15150
15205
|
const machine = detectMachineContext();
|
|
15151
15206
|
for (const pf of PROJECT_CONFIG_FILES) {
|
|
15152
|
-
const abs =
|
|
15153
|
-
if (!
|
|
15207
|
+
const abs = join18(absDir, pf.file);
|
|
15208
|
+
if (!existsSync17(abs))
|
|
15154
15209
|
continue;
|
|
15155
15210
|
try {
|
|
15156
15211
|
const rawContent = readFileSync14(abs, "utf-8");
|
|
@@ -15182,19 +15237,19 @@ async function syncProject(opts) {
|
|
|
15182
15237
|
}
|
|
15183
15238
|
}
|
|
15184
15239
|
for (const ruleDir of [
|
|
15185
|
-
{ dir:
|
|
15186
|
-
{ dir:
|
|
15187
|
-
{ dir:
|
|
15188
|
-
{ dir:
|
|
15189
|
-
{ dir:
|
|
15190
|
-
{ dir:
|
|
15191
|
-
{ dir:
|
|
15240
|
+
{ dir: join18(absDir, ".claude", "rules"), agent: "claude", namePrefix: "rules" },
|
|
15241
|
+
{ dir: join18(absDir, ".agents", "rules"), agent: "antigravity", namePrefix: "antigravity-rules" },
|
|
15242
|
+
{ dir: join18(absDir, ".cursor", "rules"), agent: "cursor", namePrefix: "cursor-rules" },
|
|
15243
|
+
{ dir: join18(absDir, ".github", "instructions"), agent: "copilot", namePrefix: "copilot-instructions" },
|
|
15244
|
+
{ dir: join18(absDir, ".devin", "rules"), agent: "devin", namePrefix: "devin-rules" },
|
|
15245
|
+
{ dir: join18(absDir, ".windsurf", "rules"), agent: "windsurf-legacy", namePrefix: "windsurf-rules" },
|
|
15246
|
+
{ dir: join18(absDir, ".clinerules"), agent: "cline", namePrefix: "cline-rules" }
|
|
15192
15247
|
]) {
|
|
15193
|
-
if (!
|
|
15248
|
+
if (!existsSync17(ruleDir.dir))
|
|
15194
15249
|
continue;
|
|
15195
|
-
const mdFiles =
|
|
15250
|
+
const mdFiles = readdirSync5(ruleDir.dir).filter((f) => f.endsWith(".md") || f.endsWith(".mdc"));
|
|
15196
15251
|
for (const f of mdFiles) {
|
|
15197
|
-
const abs =
|
|
15252
|
+
const abs = join18(ruleDir.dir, f);
|
|
15198
15253
|
const raw = readFileSync14(abs, "utf-8");
|
|
15199
15254
|
const redacted = redactContent(raw, "markdown");
|
|
15200
15255
|
const machineAware = templateizeMachineContent(redacted.content, machine);
|
|
@@ -15234,14 +15289,14 @@ async function syncKnown(opts = {}) {
|
|
|
15234
15289
|
for (const known of targets) {
|
|
15235
15290
|
if (known.rulesDir) {
|
|
15236
15291
|
const absDir = expandPath(known.rulesDir);
|
|
15237
|
-
if (!
|
|
15292
|
+
if (!existsSync17(absDir)) {
|
|
15238
15293
|
result.skipped.push(known.rulesDir);
|
|
15239
15294
|
continue;
|
|
15240
15295
|
}
|
|
15241
15296
|
const extensions = known.rulesExtensions ?? [".md", ".mdc"];
|
|
15242
|
-
const ruleFiles =
|
|
15297
|
+
const ruleFiles = readdirSync5(absDir).filter((f) => extensions.some((ext) => f.endsWith(ext)));
|
|
15243
15298
|
for (const f of ruleFiles) {
|
|
15244
|
-
const abs2 =
|
|
15299
|
+
const abs2 = join18(absDir, f);
|
|
15245
15300
|
const targetPath = abs2.replace(home, "~");
|
|
15246
15301
|
if (existingOutputOwners.has(normalizeTargetPath(targetPath)) || isKnownGeneratedTargetPath(targetPath)) {
|
|
15247
15302
|
result.skipped.push(`${targetPath} (generated output)`);
|
|
@@ -15275,7 +15330,7 @@ async function syncKnown(opts = {}) {
|
|
|
15275
15330
|
continue;
|
|
15276
15331
|
}
|
|
15277
15332
|
const abs = expandPath(known.path);
|
|
15278
|
-
if (!
|
|
15333
|
+
if (!existsSync17(abs)) {
|
|
15279
15334
|
result.skipped.push(known.path);
|
|
15280
15335
|
continue;
|
|
15281
15336
|
}
|
|
@@ -15383,7 +15438,7 @@ function storedPlaceholderIsLiteralOnDisk(storedLine, diskLine) {
|
|
|
15383
15438
|
}
|
|
15384
15439
|
function buildDiff(expectedContent, targetPath, storedFormat, showSecrets) {
|
|
15385
15440
|
const path = expandPath(targetPath);
|
|
15386
|
-
if (!
|
|
15441
|
+
if (!existsSync17(path))
|
|
15387
15442
|
return `(file not found on disk: ${path})`;
|
|
15388
15443
|
const diskContent = readFileSync14(path, "utf-8");
|
|
15389
15444
|
if (diskContent === expectedContent)
|
|
@@ -15543,15 +15598,15 @@ function detectFormat(filePath) {
|
|
|
15543
15598
|
return "text";
|
|
15544
15599
|
}
|
|
15545
15600
|
// src/lib/export.ts
|
|
15546
|
-
import { existsSync as
|
|
15547
|
-
import { join as
|
|
15601
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync8, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
15602
|
+
import { join as join19, resolve as resolve12 } from "path";
|
|
15548
15603
|
import { tmpdir } from "os";
|
|
15549
15604
|
async function exportConfigs(outputPath, opts = {}) {
|
|
15550
15605
|
const store = opts.store ?? resolveConfigStore();
|
|
15551
15606
|
const configs = await store.listConfigs(opts.filter);
|
|
15552
|
-
const absOutput =
|
|
15553
|
-
const tmpDir =
|
|
15554
|
-
const contentsDir =
|
|
15607
|
+
const absOutput = resolve12(outputPath);
|
|
15608
|
+
const tmpDir = join19(tmpdir(), `configs-export-${Date.now()}`);
|
|
15609
|
+
const contentsDir = join19(tmpDir, "contents");
|
|
15555
15610
|
try {
|
|
15556
15611
|
mkdirSync8(contentsDir, { recursive: true });
|
|
15557
15612
|
const manifest = {
|
|
@@ -15559,10 +15614,10 @@ async function exportConfigs(outputPath, opts = {}) {
|
|
|
15559
15614
|
exported_at: new Date().toISOString(),
|
|
15560
15615
|
configs: configs.map(({ content: _content, ...meta }) => meta)
|
|
15561
15616
|
};
|
|
15562
|
-
writeFileSync6(
|
|
15617
|
+
writeFileSync6(join19(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
|
|
15563
15618
|
for (const config of configs) {
|
|
15564
15619
|
const fileName = `${config.slug}.${config.format === "text" ? "txt" : config.format}`;
|
|
15565
|
-
writeFileSync6(
|
|
15620
|
+
writeFileSync6(join19(contentsDir, fileName), config.content, "utf-8");
|
|
15566
15621
|
}
|
|
15567
15622
|
const proc = Bun.spawn(["tar", "czf", absOutput, "-C", tmpDir, "."], {
|
|
15568
15623
|
stdout: "pipe",
|
|
@@ -15575,20 +15630,20 @@ async function exportConfigs(outputPath, opts = {}) {
|
|
|
15575
15630
|
}
|
|
15576
15631
|
return { path: absOutput, count: configs.length };
|
|
15577
15632
|
} finally {
|
|
15578
|
-
if (
|
|
15633
|
+
if (existsSync18(tmpDir)) {
|
|
15579
15634
|
rmSync4(tmpDir, { recursive: true, force: true });
|
|
15580
15635
|
}
|
|
15581
15636
|
}
|
|
15582
15637
|
}
|
|
15583
15638
|
// src/lib/import.ts
|
|
15584
|
-
import { existsSync as
|
|
15585
|
-
import { join as
|
|
15639
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync9, readFileSync as readFileSync15, rmSync as rmSync5 } from "fs";
|
|
15640
|
+
import { join as join20, resolve as resolve13 } from "path";
|
|
15586
15641
|
import { tmpdir as tmpdir2 } from "os";
|
|
15587
15642
|
async function importConfigs(bundlePath, opts = {}) {
|
|
15588
15643
|
const store = opts.store ?? resolveConfigStore();
|
|
15589
15644
|
const conflict = opts.conflict ?? "skip";
|
|
15590
|
-
const absPath =
|
|
15591
|
-
const tmpDir =
|
|
15645
|
+
const absPath = resolve13(bundlePath);
|
|
15646
|
+
const tmpDir = join20(tmpdir2(), `configs-import-${Date.now()}`);
|
|
15592
15647
|
const result = { created: 0, updated: 0, skipped: 0, errors: [] };
|
|
15593
15648
|
try {
|
|
15594
15649
|
mkdirSync9(tmpDir, { recursive: true });
|
|
@@ -15601,15 +15656,15 @@ async function importConfigs(bundlePath, opts = {}) {
|
|
|
15601
15656
|
const stderr = await new Response(proc.stderr).text();
|
|
15602
15657
|
throw new Error(`tar extraction failed: ${stderr}`);
|
|
15603
15658
|
}
|
|
15604
|
-
const manifestPath =
|
|
15605
|
-
if (!
|
|
15659
|
+
const manifestPath = join20(tmpDir, "manifest.json");
|
|
15660
|
+
if (!existsSync19(manifestPath))
|
|
15606
15661
|
throw new Error("Invalid bundle: missing manifest.json");
|
|
15607
15662
|
const manifest = JSON.parse(readFileSync15(manifestPath, "utf-8"));
|
|
15608
15663
|
for (const meta of manifest.configs) {
|
|
15609
15664
|
try {
|
|
15610
15665
|
const ext = meta.format === "text" ? "txt" : meta.format;
|
|
15611
|
-
const contentFile =
|
|
15612
|
-
const content =
|
|
15666
|
+
const contentFile = join20(tmpDir, "contents", `${meta.slug}.${ext}`);
|
|
15667
|
+
const content = existsSync19(contentFile) ? readFileSync15(contentFile, "utf-8") : "";
|
|
15613
15668
|
let existing = null;
|
|
15614
15669
|
try {
|
|
15615
15670
|
existing = await store.getConfig(meta.slug);
|
|
@@ -15643,16 +15698,16 @@ async function importConfigs(bundlePath, opts = {}) {
|
|
|
15643
15698
|
}
|
|
15644
15699
|
return result;
|
|
15645
15700
|
} finally {
|
|
15646
|
-
if (
|
|
15701
|
+
if (existsSync19(tmpDir)) {
|
|
15647
15702
|
rmSync5(tmpDir, { recursive: true, force: true });
|
|
15648
15703
|
}
|
|
15649
15704
|
}
|
|
15650
15705
|
}
|
|
15651
15706
|
// src/lib/package-manager-guard.ts
|
|
15652
15707
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
15653
|
-
import { existsSync as
|
|
15654
|
-
import { homedir as
|
|
15655
|
-
import { basename as basename7, dirname as
|
|
15708
|
+
import { existsSync as existsSync20, lstatSync as lstatSync7, readdirSync as readdirSync6, readFileSync as readFileSync16 } from "fs";
|
|
15709
|
+
import { homedir as homedir12 } from "os";
|
|
15710
|
+
import { basename as basename7, dirname as dirname11, isAbsolute as isAbsolute5, join as join21, relative as relative7, resolve as resolve14 } from "path";
|
|
15656
15711
|
var SKIP_DIRS = new Set([
|
|
15657
15712
|
".git",
|
|
15658
15713
|
"node_modules",
|
|
@@ -15689,12 +15744,12 @@ var TOKEN_VALUE_PATTERNS = [
|
|
|
15689
15744
|
{ re: /xoxb-[0-9]+-[A-Za-z0-9-]+/, rule: "literal-slack-token", detail: "literal Slack token-like value" }
|
|
15690
15745
|
];
|
|
15691
15746
|
function scanPackageManagerSecrets(options = {}) {
|
|
15692
|
-
const cwd = options.cwd ?
|
|
15693
|
-
const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) =>
|
|
15747
|
+
const cwd = options.cwd ? resolve14(options.cwd) : process.cwd();
|
|
15748
|
+
const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) => resolve14(cwd, root));
|
|
15694
15749
|
const findings = [];
|
|
15695
15750
|
let scannedFiles = 0;
|
|
15696
15751
|
for (const root of roots) {
|
|
15697
|
-
if (!
|
|
15752
|
+
if (!existsSync20(root))
|
|
15698
15753
|
continue;
|
|
15699
15754
|
const stat = lstatSync7(root);
|
|
15700
15755
|
if (stat.isFile()) {
|
|
@@ -15704,7 +15759,7 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
15704
15759
|
if (text === null)
|
|
15705
15760
|
continue;
|
|
15706
15761
|
scannedFiles++;
|
|
15707
|
-
findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root),
|
|
15762
|
+
findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root), dirname11(root)));
|
|
15708
15763
|
continue;
|
|
15709
15764
|
}
|
|
15710
15765
|
if (!stat.isDirectory())
|
|
@@ -15721,10 +15776,10 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
15721
15776
|
}
|
|
15722
15777
|
}
|
|
15723
15778
|
if (options.includeHome) {
|
|
15724
|
-
const home =
|
|
15779
|
+
const home = homedir12();
|
|
15725
15780
|
for (const name of HOME_FILES) {
|
|
15726
|
-
const file =
|
|
15727
|
-
if (!
|
|
15781
|
+
const file = join21(home, name);
|
|
15782
|
+
if (!existsSync20(file))
|
|
15728
15783
|
continue;
|
|
15729
15784
|
const text = readTextFile(file);
|
|
15730
15785
|
if (text === null)
|
|
@@ -15744,16 +15799,16 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
15744
15799
|
function collectRepoFiles(root) {
|
|
15745
15800
|
const out = [];
|
|
15746
15801
|
const visit = (dir) => {
|
|
15747
|
-
for (const entry of
|
|
15802
|
+
for (const entry of readdirSync6(dir, { withFileTypes: true })) {
|
|
15748
15803
|
if (entry.isDirectory()) {
|
|
15749
15804
|
if (SKIP_DIRS.has(entry.name))
|
|
15750
15805
|
continue;
|
|
15751
|
-
visit(
|
|
15806
|
+
visit(join21(dir, entry.name));
|
|
15752
15807
|
continue;
|
|
15753
15808
|
}
|
|
15754
15809
|
if (!entry.isFile())
|
|
15755
15810
|
continue;
|
|
15756
|
-
const file =
|
|
15811
|
+
const file = join21(dir, entry.name);
|
|
15757
15812
|
if (shouldScanRepoFile(file))
|
|
15758
15813
|
out.push(file);
|
|
15759
15814
|
}
|
|
@@ -15991,7 +16046,7 @@ function trackedFiles(root) {
|
|
|
15991
16046
|
}
|
|
15992
16047
|
function isTrackedFile(file) {
|
|
15993
16048
|
try {
|
|
15994
|
-
const repoRoot = execFileSync2("git", ["-C",
|
|
16049
|
+
const repoRoot = execFileSync2("git", ["-C", dirname11(file), "rev-parse", "--show-toplevel"], {
|
|
15995
16050
|
encoding: "utf-8",
|
|
15996
16051
|
stdio: ["ignore", "pipe", "ignore"]
|
|
15997
16052
|
}).trim();
|
|
@@ -16021,7 +16076,7 @@ function stripInlineComment(value) {
|
|
|
16021
16076
|
return value.replace(/\s[#;].*$/, "").trim();
|
|
16022
16077
|
}
|
|
16023
16078
|
function displayPath(file, root) {
|
|
16024
|
-
const home =
|
|
16079
|
+
const home = homedir12();
|
|
16025
16080
|
if (root === home && (file === home || file.startsWith(home + "/")))
|
|
16026
16081
|
return "~/" + toPosix(relative7(home, file));
|
|
16027
16082
|
if (isAbsolute5(root) && file.startsWith(root + "/"))
|