@openthink/stamp 2.0.1 → 2.0.2
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/index.js +158 -133
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -59,8 +59,8 @@ import { Command } from "commander";
|
|
|
59
59
|
|
|
60
60
|
// src/commands/bootstrap.ts
|
|
61
61
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
62
|
-
import { existsSync as existsSync7, readFileSync as
|
|
63
|
-
import { dirname as
|
|
62
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, readdirSync, statSync, writeFileSync as writeFileSync5 } from "fs";
|
|
63
|
+
import { dirname as dirname4, join as join4 } from "path";
|
|
64
64
|
|
|
65
65
|
// src/lib/agentsMd.ts
|
|
66
66
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -3166,9 +3166,38 @@ function readLineSync() {
|
|
|
3166
3166
|
return out;
|
|
3167
3167
|
}
|
|
3168
3168
|
|
|
3169
|
+
// src/lib/version.ts
|
|
3170
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
3171
|
+
import { dirname, join as join3 } from "path";
|
|
3172
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3173
|
+
function readPackageVersion() {
|
|
3174
|
+
const here = dirname(fileURLToPath2(import.meta.url));
|
|
3175
|
+
for (let dir = here, i = 0; i < 6; i++) {
|
|
3176
|
+
try {
|
|
3177
|
+
const raw = readFileSync4(join3(dir, "package.json"), "utf8");
|
|
3178
|
+
const pkg = JSON.parse(raw);
|
|
3179
|
+
if (pkg.name === "@openthink/stamp" && pkg.version) return pkg.version;
|
|
3180
|
+
} catch {
|
|
3181
|
+
}
|
|
3182
|
+
const parent = dirname(dir);
|
|
3183
|
+
if (parent === dir) break;
|
|
3184
|
+
dir = parent;
|
|
3185
|
+
}
|
|
3186
|
+
throw new Error("could not locate @openthink/stamp package.json to read version");
|
|
3187
|
+
}
|
|
3188
|
+
|
|
3169
3189
|
// src/lib/deprecationNotice.ts
|
|
3170
|
-
function maybePrintDeprecationNotice() {
|
|
3190
|
+
function maybePrintDeprecationNotice(versionOverride) {
|
|
3171
3191
|
if (process.env.STAMP_SUPPRESS_DEPRECATION === "1") return;
|
|
3192
|
+
let major = null;
|
|
3193
|
+
try {
|
|
3194
|
+
const version = versionOverride ?? readPackageVersion();
|
|
3195
|
+
const m = /^(\d+)\./.exec(version);
|
|
3196
|
+
if (m) major = Number.parseInt(m[1], 10);
|
|
3197
|
+
} catch {
|
|
3198
|
+
return;
|
|
3199
|
+
}
|
|
3200
|
+
if (major === null || major >= 2) return;
|
|
3172
3201
|
process.stderr.write(
|
|
3173
3202
|
"warning: stamp 1.x is in maintenance \u2014 the server-attested 2.x line is active. See docs/migration-1.x-to-2.x.md. Suppress: STAMP_SUPPRESS_DEPRECATION=1.\n"
|
|
3174
3203
|
);
|
|
@@ -3763,12 +3792,12 @@ ${close}`;
|
|
|
3763
3792
|
import {
|
|
3764
3793
|
existsSync as existsSync3,
|
|
3765
3794
|
mkdirSync,
|
|
3766
|
-
readFileSync as
|
|
3795
|
+
readFileSync as readFileSync5,
|
|
3767
3796
|
renameSync,
|
|
3768
3797
|
unlinkSync,
|
|
3769
3798
|
writeFileSync as writeFileSync2
|
|
3770
3799
|
} from "fs";
|
|
3771
|
-
import { dirname } from "path";
|
|
3800
|
+
import { dirname as dirname2 } from "path";
|
|
3772
3801
|
import { parse as parseYaml6, stringify as stringifyYaml } from "yaml";
|
|
3773
3802
|
var DEFAULT_REVIEWER_MODELS = {
|
|
3774
3803
|
security: "claude-sonnet-4-6",
|
|
@@ -3788,7 +3817,7 @@ function loadUserConfig() {
|
|
|
3788
3817
|
if (!existsSync3(path2)) return null;
|
|
3789
3818
|
let raw;
|
|
3790
3819
|
try {
|
|
3791
|
-
raw =
|
|
3820
|
+
raw = readFileSync5(path2, "utf8");
|
|
3792
3821
|
} catch (err) {
|
|
3793
3822
|
throw new Error(
|
|
3794
3823
|
`failed to read ${path2}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -3848,7 +3877,7 @@ function stringifyUserConfig(cfg) {
|
|
|
3848
3877
|
}
|
|
3849
3878
|
function writeUserConfig(cfg) {
|
|
3850
3879
|
const path2 = userConfigPath();
|
|
3851
|
-
const dir =
|
|
3880
|
+
const dir = dirname2(path2);
|
|
3852
3881
|
if (!existsSync3(dir)) mkdirSync(dir, { recursive: true, mode: 448 });
|
|
3853
3882
|
const tmp = `${path2}.tmp.${process.pid}`;
|
|
3854
3883
|
writeFileSync2(tmp, stringifyUserConfig(cfg), { mode: 384 });
|
|
@@ -4659,7 +4688,7 @@ function stripLastLineVerdict(text) {
|
|
|
4659
4688
|
|
|
4660
4689
|
// src/lib/llmNotice.ts
|
|
4661
4690
|
import { existsSync as existsSync4, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
4662
|
-
import { dirname as
|
|
4691
|
+
import { dirname as dirname3 } from "path";
|
|
4663
4692
|
function maybePrintLlmNotice(repoRoot) {
|
|
4664
4693
|
if (process.env.STAMP_SUPPRESS_LLM_NOTICE === "1") return;
|
|
4665
4694
|
const marker = stampLlmNoticeMarkerPath(repoRoot);
|
|
@@ -4672,7 +4701,7 @@ function maybePrintLlmNotice(repoRoot) {
|
|
|
4672
4701
|
`
|
|
4673
4702
|
);
|
|
4674
4703
|
try {
|
|
4675
|
-
mkdirSync3(
|
|
4704
|
+
mkdirSync3(dirname3(marker), { recursive: true });
|
|
4676
4705
|
writeFileSync4(marker, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
4677
4706
|
`);
|
|
4678
4707
|
} catch {
|
|
@@ -4912,7 +4941,7 @@ import { spawnSync as spawnSync4 } from "child_process";
|
|
|
4912
4941
|
import { createInterface } from "readline";
|
|
4913
4942
|
|
|
4914
4943
|
// src/lib/serverConfig.ts
|
|
4915
|
-
import { existsSync as existsSync5, readFileSync as
|
|
4944
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
4916
4945
|
import { parse as parseYaml7 } from "yaml";
|
|
4917
4946
|
var DEFAULT_USER = "git";
|
|
4918
4947
|
var DEFAULT_REPO_ROOT = "/srv/git";
|
|
@@ -4942,7 +4971,7 @@ function loadServerConfig() {
|
|
|
4942
4971
|
if (!existsSync5(path2)) return null;
|
|
4943
4972
|
let raw;
|
|
4944
4973
|
try {
|
|
4945
|
-
raw =
|
|
4974
|
+
raw = readFileSync6(path2, "utf8");
|
|
4946
4975
|
} catch (err) {
|
|
4947
4976
|
throw new Error(
|
|
4948
4977
|
`failed to read ${path2}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -5921,27 +5950,27 @@ function readSeedDir(seedDir) {
|
|
|
5921
5950
|
if (!existsSync7(seedDir) || !statSync(seedDir).isDirectory()) {
|
|
5922
5951
|
throw new Error(`--from path is not a directory: ${seedDir}`);
|
|
5923
5952
|
}
|
|
5924
|
-
const configPath =
|
|
5953
|
+
const configPath = join4(seedDir, "config.yml");
|
|
5925
5954
|
if (!existsSync7(configPath)) {
|
|
5926
5955
|
throw new Error(`--from dir missing config.yml: ${configPath}`);
|
|
5927
5956
|
}
|
|
5928
|
-
const reviewersDir =
|
|
5957
|
+
const reviewersDir = join4(seedDir, "reviewers");
|
|
5929
5958
|
if (!existsSync7(reviewersDir) || !statSync(reviewersDir).isDirectory()) {
|
|
5930
5959
|
throw new Error(`--from dir missing reviewers/ subdirectory: ${reviewersDir}`);
|
|
5931
5960
|
}
|
|
5932
|
-
const yaml =
|
|
5961
|
+
const yaml = readFileSync7(configPath, "utf8");
|
|
5933
5962
|
const config2 = parseConfigFromYaml(yaml);
|
|
5934
5963
|
const reviewerFiles = /* @__PURE__ */ new Map();
|
|
5935
5964
|
for (const entry of readdirSync(reviewersDir)) {
|
|
5936
|
-
const full =
|
|
5965
|
+
const full = join4(reviewersDir, entry);
|
|
5937
5966
|
if (statSync(full).isFile()) {
|
|
5938
|
-
reviewerFiles.set(`.stamp/reviewers/${entry}`,
|
|
5967
|
+
reviewerFiles.set(`.stamp/reviewers/${entry}`, readFileSync7(full, "utf8"));
|
|
5939
5968
|
}
|
|
5940
5969
|
}
|
|
5941
5970
|
let mirrorYml;
|
|
5942
|
-
const mirrorPath =
|
|
5971
|
+
const mirrorPath = join4(seedDir, "mirror.yml");
|
|
5943
5972
|
if (existsSync7(mirrorPath)) {
|
|
5944
|
-
mirrorYml =
|
|
5973
|
+
mirrorYml = readFileSync7(mirrorPath, "utf8");
|
|
5945
5974
|
}
|
|
5946
5975
|
return { config: config2, reviewerFiles, mirrorYml };
|
|
5947
5976
|
}
|
|
@@ -5949,12 +5978,12 @@ function writeBootstrapFiles(repoRoot, plan) {
|
|
|
5949
5978
|
ensureDir(stampConfigDir(repoRoot));
|
|
5950
5979
|
ensureDir(stampReviewersDir(repoRoot));
|
|
5951
5980
|
for (const { path: path2, content } of plan.reviewerFiles.values()) {
|
|
5952
|
-
const full =
|
|
5953
|
-
ensureDir(
|
|
5981
|
+
const full = join4(repoRoot, path2);
|
|
5982
|
+
ensureDir(dirname4(full));
|
|
5954
5983
|
writeFileSync5(full, content);
|
|
5955
5984
|
}
|
|
5956
5985
|
if (plan.mirrorYml !== void 0) {
|
|
5957
|
-
writeFileSync5(
|
|
5986
|
+
writeFileSync5(join4(repoRoot, ".stamp/mirror.yml"), plan.mirrorYml);
|
|
5958
5987
|
}
|
|
5959
5988
|
writeFileSync5(stampConfigFile(repoRoot), stringifyConfig(plan.newConfig));
|
|
5960
5989
|
}
|
|
@@ -5997,8 +6026,8 @@ function branchExists(name, cwd) {
|
|
|
5997
6026
|
}
|
|
5998
6027
|
|
|
5999
6028
|
// src/commands/init.ts
|
|
6000
|
-
import { existsSync as existsSync9, readFileSync as
|
|
6001
|
-
import { dirname as
|
|
6029
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
|
|
6030
|
+
import { dirname as dirname5, join as join6 } from "path";
|
|
6002
6031
|
|
|
6003
6032
|
// src/lib/ghRuleset.ts
|
|
6004
6033
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
@@ -6334,14 +6363,14 @@ function computeDesiredBypassActors(current, deployKeyId, flags) {
|
|
|
6334
6363
|
}
|
|
6335
6364
|
|
|
6336
6365
|
// src/lib/oteamConfig.ts
|
|
6337
|
-
import { existsSync as existsSync8, readFileSync as
|
|
6366
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8, renameSync as renameSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
6338
6367
|
import { homedir } from "os";
|
|
6339
|
-
import { join as
|
|
6340
|
-
var OTEAM_CONFIG_PATH =
|
|
6368
|
+
import { join as join5 } from "path";
|
|
6369
|
+
var OTEAM_CONFIG_PATH = join5(homedir(), ".open-team", "config.json");
|
|
6341
6370
|
function readOteamConfig(configPath = OTEAM_CONFIG_PATH) {
|
|
6342
6371
|
if (!existsSync8(configPath)) return null;
|
|
6343
6372
|
try {
|
|
6344
|
-
const parsed = JSON.parse(
|
|
6373
|
+
const parsed = JSON.parse(readFileSync8(configPath, "utf8"));
|
|
6345
6374
|
if (Array.isArray(parsed)) {
|
|
6346
6375
|
throw new Error("config must be a JSON object, not an array");
|
|
6347
6376
|
}
|
|
@@ -6356,7 +6385,7 @@ function patchStampHost(host, configPath = OTEAM_CONFIG_PATH) {
|
|
|
6356
6385
|
let config2 = {};
|
|
6357
6386
|
if (existsSync8(configPath)) {
|
|
6358
6387
|
try {
|
|
6359
|
-
const parsed = JSON.parse(
|
|
6388
|
+
const parsed = JSON.parse(readFileSync8(configPath, "utf8"));
|
|
6360
6389
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
6361
6390
|
config2 = parsed;
|
|
6362
6391
|
}
|
|
@@ -6382,6 +6411,7 @@ function patchStampHost(host, configPath = OTEAM_CONFIG_PATH) {
|
|
|
6382
6411
|
}
|
|
6383
6412
|
|
|
6384
6413
|
// src/commands/init.ts
|
|
6414
|
+
var DEFAULT_ACTION_SOURCE = "OpenThinkAi/stamp-cli";
|
|
6385
6415
|
function runInit(opts = {}) {
|
|
6386
6416
|
maybePrintDeprecationNotice();
|
|
6387
6417
|
const repoRoot = findRepoRoot();
|
|
@@ -6412,26 +6442,26 @@ For local-only / advisory use against this GitHub repo: re-run with \`stamp init
|
|
|
6412
6442
|
if (!alreadyHasConfig) {
|
|
6413
6443
|
if (opts.minimal) {
|
|
6414
6444
|
writeFileSync7(configFile, stringifyConfig(MINIMAL_CONFIG));
|
|
6415
|
-
writeFileSync7(
|
|
6445
|
+
writeFileSync7(join6(reviewersDir, "example.md"), EXAMPLE_REVIEWER_PROMPT);
|
|
6416
6446
|
} else {
|
|
6417
6447
|
writeFileSync7(configFile, stringifyConfig(DEFAULT_CONFIG));
|
|
6418
6448
|
writeFileSync7(
|
|
6419
|
-
|
|
6449
|
+
join6(reviewersDir, "security.md"),
|
|
6420
6450
|
DEFAULT_SECURITY_PROMPT
|
|
6421
6451
|
);
|
|
6422
6452
|
writeFileSync7(
|
|
6423
|
-
|
|
6453
|
+
join6(reviewersDir, "standards.md"),
|
|
6424
6454
|
DEFAULT_STANDARDS_PROMPT
|
|
6425
6455
|
);
|
|
6426
6456
|
writeFileSync7(
|
|
6427
|
-
|
|
6457
|
+
join6(reviewersDir, "product.md"),
|
|
6428
6458
|
DEFAULT_PRODUCT_PROMPT
|
|
6429
6459
|
);
|
|
6430
6460
|
}
|
|
6431
6461
|
}
|
|
6432
6462
|
const { keypair, created: keyCreated } = ensureUserKeypair();
|
|
6433
6463
|
const userCfg = loadOrCreateUserConfig();
|
|
6434
|
-
const pubKeyPath =
|
|
6464
|
+
const pubKeyPath = join6(
|
|
6435
6465
|
trustedKeysDir,
|
|
6436
6466
|
publicKeyFingerprintFilename(keypair.fingerprint)
|
|
6437
6467
|
);
|
|
@@ -6445,7 +6475,8 @@ For local-only / advisory use against this GitHub repo: re-run with \`stamp init
|
|
|
6445
6475
|
const prCheckResult = maybeWriteVerifyWorkflow(
|
|
6446
6476
|
repoRoot,
|
|
6447
6477
|
opts.prCheck,
|
|
6448
|
-
effectiveMode
|
|
6478
|
+
effectiveMode,
|
|
6479
|
+
opts.actionSource ?? DEFAULT_ACTION_SOURCE
|
|
6449
6480
|
);
|
|
6450
6481
|
const prModeResult = opts.prMode ? maybeWritePrModeMirrorWorkflow(repoRoot, { force: opts.prModeForce }) : { action: "skipped", path: PR_MODE_WORKFLOW_PATH, substitution: null };
|
|
6451
6482
|
const agentsMdAction = opts.agentsMd === false ? "skipped" : ensureAgentsMd(repoRoot, effectiveMode);
|
|
@@ -6597,8 +6628,8 @@ function runBootstrapCommit(repoRoot, scaffoldOrSync) {
|
|
|
6597
6628
|
return { kind: "skipped-already-tracked" };
|
|
6598
6629
|
}
|
|
6599
6630
|
const toAdd = [".stamp"];
|
|
6600
|
-
if (existsSync9(
|
|
6601
|
-
if (existsSync9(
|
|
6631
|
+
if (existsSync9(join6(repoRoot, "AGENTS.md"))) toAdd.push("AGENTS.md");
|
|
6632
|
+
if (existsSync9(join6(repoRoot, "CLAUDE.md"))) toAdd.push("CLAUDE.md");
|
|
6602
6633
|
runGit(["add", ...toAdd], repoRoot);
|
|
6603
6634
|
let hasStagedChanges = false;
|
|
6604
6635
|
try {
|
|
@@ -6803,27 +6834,27 @@ function resolveMode(userMode, remoteClass) {
|
|
|
6803
6834
|
}
|
|
6804
6835
|
}
|
|
6805
6836
|
var VERIFY_ACTION_REF = "v1.6.1";
|
|
6806
|
-
function maybeWriteVerifyWorkflow(repoRoot, prCheckOpt, effectiveMode) {
|
|
6837
|
+
function maybeWriteVerifyWorkflow(repoRoot, prCheckOpt, effectiveMode, actionSource = DEFAULT_ACTION_SOURCE) {
|
|
6807
6838
|
const path2 = ".github/workflows/stamp-verify.yml";
|
|
6808
|
-
const fullPath =
|
|
6839
|
+
const fullPath = join6(repoRoot, path2);
|
|
6809
6840
|
const defaultForMode = effectiveMode !== "server-gated";
|
|
6810
6841
|
const shouldWrite = prCheckOpt ?? defaultForMode;
|
|
6811
6842
|
if (!shouldWrite) return { action: "skipped", path: path2 };
|
|
6812
6843
|
if (existsSync9(fullPath)) {
|
|
6813
6844
|
return { action: "exists", path: path2 };
|
|
6814
6845
|
}
|
|
6815
|
-
ensureDir(
|
|
6816
|
-
writeFileSync7(fullPath, renderVerifyWorkflow());
|
|
6846
|
+
ensureDir(dirname5(fullPath));
|
|
6847
|
+
writeFileSync7(fullPath, renderVerifyWorkflow(actionSource));
|
|
6817
6848
|
return { action: "wrote", path: path2 };
|
|
6818
6849
|
}
|
|
6819
6850
|
var PR_MODE_WORKFLOW_PATH = ".github/workflows/stamp-mirror.yml";
|
|
6820
6851
|
function maybeWritePrModeMirrorWorkflow(repoRoot, opts = {}) {
|
|
6821
|
-
const fullPath =
|
|
6852
|
+
const fullPath = join6(repoRoot, PR_MODE_WORKFLOW_PATH);
|
|
6822
6853
|
const substitution = derivePrModeSubstitution(repoRoot);
|
|
6823
6854
|
if (existsSync9(fullPath) && !opts.force) {
|
|
6824
6855
|
return { action: "exists", path: PR_MODE_WORKFLOW_PATH, substitution };
|
|
6825
6856
|
}
|
|
6826
|
-
ensureDir(
|
|
6857
|
+
ensureDir(dirname5(fullPath));
|
|
6827
6858
|
writeFileSync7(fullPath, renderMirrorWorkflow(substitution));
|
|
6828
6859
|
return { action: "wrote", path: PR_MODE_WORKFLOW_PATH, substitution };
|
|
6829
6860
|
}
|
|
@@ -6831,9 +6862,9 @@ function derivePrModeSubstitution(repoRoot) {
|
|
|
6831
6862
|
let host = null;
|
|
6832
6863
|
let port = null;
|
|
6833
6864
|
try {
|
|
6834
|
-
const configFile =
|
|
6865
|
+
const configFile = join6(repoRoot, ".stamp", "config.yml");
|
|
6835
6866
|
if (existsSync9(configFile)) {
|
|
6836
|
-
const cfg = parseConfigFromYaml(
|
|
6867
|
+
const cfg = parseConfigFromYaml(readFileSync9(configFile, "utf8"));
|
|
6837
6868
|
for (const rule of Object.values(cfg.branches)) {
|
|
6838
6869
|
if (rule.review_server) {
|
|
6839
6870
|
try {
|
|
@@ -7073,7 +7104,7 @@ function printPrModeWalkthrough(result) {
|
|
|
7073
7104
|
);
|
|
7074
7105
|
console.log();
|
|
7075
7106
|
}
|
|
7076
|
-
function renderVerifyWorkflow() {
|
|
7107
|
+
function renderVerifyWorkflow(actionSource = DEFAULT_ACTION_SOURCE) {
|
|
7077
7108
|
return [
|
|
7078
7109
|
"name: stamp verify",
|
|
7079
7110
|
"",
|
|
@@ -7106,18 +7137,18 @@ function renderVerifyWorkflow() {
|
|
|
7106
7137
|
" # would force per-step refetches.",
|
|
7107
7138
|
" fetch-depth: 0",
|
|
7108
7139
|
" - name: stamp/verify-attestation",
|
|
7109
|
-
` uses:
|
|
7140
|
+
` uses: ${actionSource}/.github/actions/verify-attestation@${VERIFY_ACTION_REF}`,
|
|
7110
7141
|
""
|
|
7111
7142
|
].join("\n");
|
|
7112
7143
|
}
|
|
7113
7144
|
|
|
7114
7145
|
// src/commands/migrateServerAttested.ts
|
|
7115
|
-
import { existsSync as existsSync10, readFileSync as
|
|
7116
|
-
import { dirname as
|
|
7146
|
+
import { existsSync as existsSync10, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "fs";
|
|
7147
|
+
import { dirname as dirname6, join as join8, relative } from "path";
|
|
7117
7148
|
|
|
7118
7149
|
// src/lib/migrateServerAttested.ts
|
|
7119
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
7120
|
-
import { join as
|
|
7150
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync10, statSync as statSync2 } from "fs";
|
|
7151
|
+
import { join as join7 } from "path";
|
|
7121
7152
|
function detectExistingKeys(repoRoot, onSkip) {
|
|
7122
7153
|
const dir = stampTrustedKeysDir(repoRoot);
|
|
7123
7154
|
let entries;
|
|
@@ -7131,7 +7162,7 @@ function detectExistingKeys(repoRoot, onSkip) {
|
|
|
7131
7162
|
const out = [];
|
|
7132
7163
|
for (const filename of entries.sort()) {
|
|
7133
7164
|
if (!filename.endsWith(".pub")) continue;
|
|
7134
|
-
const full =
|
|
7165
|
+
const full = join7(dir, filename);
|
|
7135
7166
|
try {
|
|
7136
7167
|
const st = statSync2(full);
|
|
7137
7168
|
if (!st.isFile()) continue;
|
|
@@ -7140,7 +7171,7 @@ function detectExistingKeys(repoRoot, onSkip) {
|
|
|
7140
7171
|
}
|
|
7141
7172
|
let pem;
|
|
7142
7173
|
try {
|
|
7143
|
-
pem =
|
|
7174
|
+
pem = readFileSync10(full, "utf8");
|
|
7144
7175
|
} catch (err) {
|
|
7145
7176
|
onSkip?.(filename, err instanceof Error ? err.message : String(err));
|
|
7146
7177
|
continue;
|
|
@@ -7328,7 +7359,7 @@ function runMigrateToServerAttested(opts = {}) {
|
|
|
7328
7359
|
console.error(`note: skipping .stamp/trusted-keys/${filename} \u2014 ${reason}`);
|
|
7329
7360
|
})
|
|
7330
7361
|
);
|
|
7331
|
-
const manifestPath =
|
|
7362
|
+
const manifestPath = join8(repoRoot, MANIFEST_RELATIVE_PATH);
|
|
7332
7363
|
const manifestExists = existsSync10(manifestPath);
|
|
7333
7364
|
let adminFingerprints;
|
|
7334
7365
|
if (manifestExists) {
|
|
@@ -7361,7 +7392,7 @@ function runMigrateToServerAttested(opts = {}) {
|
|
|
7361
7392
|
`expected .stamp/config.yml at ${cfgPath} but found none. Run \`stamp init\` first.`
|
|
7362
7393
|
);
|
|
7363
7394
|
}
|
|
7364
|
-
const cfgInput =
|
|
7395
|
+
const cfgInput = readFileSync11(cfgPath, "utf8");
|
|
7365
7396
|
const rewrite = rewriteConfigForMigration(cfgInput);
|
|
7366
7397
|
if (dryRun) {
|
|
7367
7398
|
printDryRun({
|
|
@@ -7385,10 +7416,10 @@ function runMigrateToServerAttested(opts = {}) {
|
|
|
7385
7416
|
console.error(`warning: ${w}`);
|
|
7386
7417
|
}
|
|
7387
7418
|
let manifestAction;
|
|
7388
|
-
if (manifestExists &&
|
|
7419
|
+
if (manifestExists && readFileSync11(manifestPath, "utf8") === manifestText) {
|
|
7389
7420
|
manifestAction = "unchanged";
|
|
7390
7421
|
} else {
|
|
7391
|
-
ensureDir(
|
|
7422
|
+
ensureDir(dirname6(manifestPath));
|
|
7392
7423
|
writeFileSync8(manifestPath, manifestText);
|
|
7393
7424
|
manifestAction = "wrote";
|
|
7394
7425
|
}
|
|
@@ -7454,7 +7485,7 @@ function readExistingAdminFingerprints(manifestPath) {
|
|
|
7454
7485
|
const out = /* @__PURE__ */ new Set();
|
|
7455
7486
|
let text;
|
|
7456
7487
|
try {
|
|
7457
|
-
text =
|
|
7488
|
+
text = readFileSync11(manifestPath, "utf8");
|
|
7458
7489
|
} catch {
|
|
7459
7490
|
return out;
|
|
7460
7491
|
}
|
|
@@ -7529,9 +7560,9 @@ function indent(text, prefix) {
|
|
|
7529
7560
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
7530
7561
|
import { request as httpRequest } from "http";
|
|
7531
7562
|
import { request as httpsRequest } from "https";
|
|
7532
|
-
import { existsSync as existsSync11, readFileSync as
|
|
7563
|
+
import { existsSync as existsSync11, readFileSync as readFileSync12 } from "fs";
|
|
7533
7564
|
import { homedir as homedir2, hostname, userInfo } from "os";
|
|
7534
|
-
import { join as
|
|
7565
|
+
import { join as join9 } from "path";
|
|
7535
7566
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
7536
7567
|
|
|
7537
7568
|
// src/lib/inviteUrl.ts
|
|
@@ -7698,10 +7729,10 @@ function runInvitesMint(opts) {
|
|
|
7698
7729
|
}
|
|
7699
7730
|
}
|
|
7700
7731
|
function defaultSshPubkeyPath() {
|
|
7701
|
-
return
|
|
7732
|
+
return join9(homedir2(), ".ssh", "id_ed25519.pub");
|
|
7702
7733
|
}
|
|
7703
7734
|
function defaultStampPubkeyPath() {
|
|
7704
|
-
return
|
|
7735
|
+
return join9(homedir2(), ".stamp", "keys", "ed25519.pub");
|
|
7705
7736
|
}
|
|
7706
7737
|
function defaultShortName() {
|
|
7707
7738
|
const u = userInfo().username || "user";
|
|
@@ -7714,13 +7745,13 @@ function loadSshPubkey(path2) {
|
|
|
7714
7745
|
`SSH pubkey not found at ${path2}. Generate one with \`ssh-keygen -t ed25519\` or pass --ssh-pubkey <path>.`
|
|
7715
7746
|
);
|
|
7716
7747
|
}
|
|
7717
|
-
const raw =
|
|
7748
|
+
const raw = readFileSync12(path2, "utf8");
|
|
7718
7749
|
const parsed = parseSshPubkey(raw);
|
|
7719
7750
|
return { path: path2, full: parsed.full, fingerprint: parsed.fingerprint };
|
|
7720
7751
|
}
|
|
7721
7752
|
function loadStampPubkey(path2) {
|
|
7722
7753
|
if (!existsSync11(path2)) return null;
|
|
7723
|
-
const pem =
|
|
7754
|
+
const pem = readFileSync12(path2, "utf8");
|
|
7724
7755
|
try {
|
|
7725
7756
|
const fp = fingerprintFromPem(pem);
|
|
7726
7757
|
return { path: path2, pem, fingerprint: fp };
|
|
@@ -7865,15 +7896,15 @@ async function runInvitesAccept(opts) {
|
|
|
7865
7896
|
|
|
7866
7897
|
// src/commands/provision.ts
|
|
7867
7898
|
import { spawnSync as spawnSync8 } from "child_process";
|
|
7868
|
-
import { existsSync as existsSync13, mkdtempSync, readFileSync as
|
|
7899
|
+
import { existsSync as existsSync13, mkdtempSync, readFileSync as readFileSync13, rmSync, writeFileSync as writeFileSync10 } from "fs";
|
|
7869
7900
|
import { tmpdir } from "os";
|
|
7870
|
-
import { join as
|
|
7901
|
+
import { join as join10, resolve as resolvePath } from "path";
|
|
7871
7902
|
import { parse as parseYaml8 } from "yaml";
|
|
7872
7903
|
|
|
7873
7904
|
// src/commands/server.ts
|
|
7874
7905
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
7875
7906
|
import { existsSync as existsSync12, mkdirSync as mkdirSync4, renameSync as renameSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync9 } from "fs";
|
|
7876
|
-
import { dirname as
|
|
7907
|
+
import { dirname as dirname7 } from "path";
|
|
7877
7908
|
import { stringify as stringifyYaml2 } from "yaml";
|
|
7878
7909
|
|
|
7879
7910
|
// src/lib/perRepoKey.ts
|
|
@@ -8032,7 +8063,7 @@ function writeConfig(opts) {
|
|
|
8032
8063
|
repoRootPrefix: opts.repoRootPrefix
|
|
8033
8064
|
});
|
|
8034
8065
|
const path2 = userServerConfigPath();
|
|
8035
|
-
const dir =
|
|
8066
|
+
const dir = dirname7(path2);
|
|
8036
8067
|
if (!existsSync12(dir)) mkdirSync4(dir, { recursive: true, mode: 448 });
|
|
8037
8068
|
const tmp = `${path2}.tmp.${process.pid}`;
|
|
8038
8069
|
writeFileSync9(tmp, yaml, { mode: 384 });
|
|
@@ -8345,9 +8376,9 @@ async function runMigrateExisting(opts, server2) {
|
|
|
8345
8376
|
console.log("\n(dry run \u2014 no changes made)");
|
|
8346
8377
|
return;
|
|
8347
8378
|
}
|
|
8348
|
-
const stagingDir = mkdtempSync(
|
|
8349
|
-
const bareCloneDir =
|
|
8350
|
-
const tarballPath =
|
|
8379
|
+
const stagingDir = mkdtempSync(join10(tmpdir(), "stamp-migrate-"));
|
|
8380
|
+
const bareCloneDir = join10(stagingDir, `${opts.name}.git`);
|
|
8381
|
+
const tarballPath = join10(stagingDir, `${opts.name}.tar.gz`);
|
|
8351
8382
|
try {
|
|
8352
8383
|
console.log(`
|
|
8353
8384
|
Building bare-clone tarball of existing repo`);
|
|
@@ -8383,9 +8414,9 @@ function ensureCwdIsGitRepo(cwd) {
|
|
|
8383
8414
|
}
|
|
8384
8415
|
}
|
|
8385
8416
|
function ensureStampInitDone(cwd) {
|
|
8386
|
-
if (!existsSync13(
|
|
8417
|
+
if (!existsSync13(join10(cwd, ".stamp", "config.yml"))) {
|
|
8387
8418
|
throw new Error(
|
|
8388
|
-
`--migrate-existing expects this repo to already be stamp-init'd (${
|
|
8419
|
+
`--migrate-existing expects this repo to already be stamp-init'd (${join10(cwd, ".stamp/config.yml")} not found). Run \`stamp init --mode local-only\` first, calibrate your reviewers, then re-run with --migrate-existing.`
|
|
8389
8420
|
);
|
|
8390
8421
|
}
|
|
8391
8422
|
}
|
|
@@ -8510,7 +8541,7 @@ mirror.yml was added to .stamp/. Commit it through the normal stamp flow:`);
|
|
|
8510
8541
|
}
|
|
8511
8542
|
}
|
|
8512
8543
|
function readMirrorYmlGithubRepo(repoRoot) {
|
|
8513
|
-
const path2 =
|
|
8544
|
+
const path2 = join10(repoRoot, ".stamp", "mirror.yml");
|
|
8514
8545
|
if (!existsSync13(path2)) {
|
|
8515
8546
|
throw new Error(
|
|
8516
8547
|
`${path2} not found \u2014 --migrate-bypass operates on an already-server-gated repo, but this cwd has no .stamp/mirror.yml. If the repo is not yet server-gated, provision it first with \`stamp provision --migrate-existing\`.`
|
|
@@ -8518,7 +8549,7 @@ function readMirrorYmlGithubRepo(repoRoot) {
|
|
|
8518
8549
|
}
|
|
8519
8550
|
let raw;
|
|
8520
8551
|
try {
|
|
8521
|
-
raw =
|
|
8552
|
+
raw = readFileSync13(path2, "utf8");
|
|
8522
8553
|
} catch (err) {
|
|
8523
8554
|
throw new Error(
|
|
8524
8555
|
`could not read ${path2}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -8758,10 +8789,10 @@ import {
|
|
|
8758
8789
|
import {
|
|
8759
8790
|
existsSync as existsSync14,
|
|
8760
8791
|
readdirSync as readdirSync3,
|
|
8761
|
-
readFileSync as
|
|
8792
|
+
readFileSync as readFileSync14,
|
|
8762
8793
|
writeFileSync as writeFileSync11
|
|
8763
8794
|
} from "fs";
|
|
8764
|
-
import { join as
|
|
8795
|
+
import { join as join11, resolve } from "path";
|
|
8765
8796
|
var USERS_EXIT = {
|
|
8766
8797
|
OK: 0,
|
|
8767
8798
|
CONFIG: 1,
|
|
@@ -8820,10 +8851,10 @@ function findExistingTrustedKey(repoRoot, fingerprint) {
|
|
|
8820
8851
|
if (!existsSync14(dir)) return null;
|
|
8821
8852
|
for (const f of readdirSync3(dir)) {
|
|
8822
8853
|
if (!f.endsWith(".pub")) continue;
|
|
8823
|
-
const fullPath =
|
|
8854
|
+
const fullPath = join11(dir, f);
|
|
8824
8855
|
let pem;
|
|
8825
8856
|
try {
|
|
8826
|
-
pem =
|
|
8857
|
+
pem = readFileSync14(fullPath, "utf8");
|
|
8827
8858
|
} catch {
|
|
8828
8859
|
continue;
|
|
8829
8860
|
}
|
|
@@ -8875,7 +8906,7 @@ function runTrustGrant(opts) {
|
|
|
8875
8906
|
);
|
|
8876
8907
|
}
|
|
8877
8908
|
const repoRoot = resolve(opts.repoPath ?? process.cwd());
|
|
8878
|
-
if (!existsSync14(
|
|
8909
|
+
if (!existsSync14(join11(repoRoot, ".git"))) {
|
|
8879
8910
|
throw new UsageError(`${repoRoot} is not a git repository`);
|
|
8880
8911
|
}
|
|
8881
8912
|
if (!existsSync14(stampConfigDir(repoRoot))) {
|
|
@@ -8917,7 +8948,7 @@ function runTrustGrant(opts) {
|
|
|
8917
8948
|
runGit2(["checkout", "-b", branch], repoRoot);
|
|
8918
8949
|
const keysDir = stampTrustedKeysDir(repoRoot);
|
|
8919
8950
|
ensureDir(keysDir, 493);
|
|
8920
|
-
const keyFile =
|
|
8951
|
+
const keyFile = join11(keysDir, `${opts.shortName}.pub`);
|
|
8921
8952
|
writeFileSync11(keyFile, pem, { mode: 420 });
|
|
8922
8953
|
runGit2(["add", keyFile], repoRoot);
|
|
8923
8954
|
runGit2(
|
|
@@ -9115,8 +9146,8 @@ function runUsersRemove(opts) {
|
|
|
9115
9146
|
}
|
|
9116
9147
|
|
|
9117
9148
|
// src/commands/keys.ts
|
|
9118
|
-
import { existsSync as existsSync15, readdirSync as readdirSync4, readFileSync as
|
|
9119
|
-
import { basename, join as
|
|
9149
|
+
import { existsSync as existsSync15, readdirSync as readdirSync4, readFileSync as readFileSync15, writeFileSync as writeFileSync12 } from "fs";
|
|
9150
|
+
import { basename, join as join12 } from "path";
|
|
9120
9151
|
function keysGenerate() {
|
|
9121
9152
|
const existing = loadUserKeypair();
|
|
9122
9153
|
if (existing) {
|
|
@@ -9160,7 +9191,7 @@ function keysList() {
|
|
|
9160
9191
|
}
|
|
9161
9192
|
for (const file of pubFiles.sort()) {
|
|
9162
9193
|
try {
|
|
9163
|
-
const pem =
|
|
9194
|
+
const pem = readFileSync15(join12(trustedDir, file), "utf8");
|
|
9164
9195
|
const fp = fingerprintFromPem(pem);
|
|
9165
9196
|
const marker = local && fp === local.fingerprint ? " (you)" : "";
|
|
9166
9197
|
console.log(` ${fp}${marker} [${file}]`);
|
|
@@ -9187,7 +9218,7 @@ function keysTrust(pubFile) {
|
|
|
9187
9218
|
if (!existsSync15(pubFile)) {
|
|
9188
9219
|
throw new Error(`public key file not found: ${pubFile}`);
|
|
9189
9220
|
}
|
|
9190
|
-
const pem =
|
|
9221
|
+
const pem = readFileSync15(pubFile, "utf8");
|
|
9191
9222
|
let fingerprint;
|
|
9192
9223
|
try {
|
|
9193
9224
|
fingerprint = fingerprintFromPem(pem);
|
|
@@ -9197,7 +9228,7 @@ function keysTrust(pubFile) {
|
|
|
9197
9228
|
);
|
|
9198
9229
|
}
|
|
9199
9230
|
const filename = publicKeyFingerprintFilename(fingerprint);
|
|
9200
|
-
const dest =
|
|
9231
|
+
const dest = join12(trustedDir, filename);
|
|
9201
9232
|
if (existsSync15(dest)) {
|
|
9202
9233
|
console.log(`${fingerprint} is already trusted (${basename(dest)})`);
|
|
9203
9234
|
return;
|
|
@@ -9657,14 +9688,14 @@ function gitDiffBetween(repoRoot, base, head) {
|
|
|
9657
9688
|
|
|
9658
9689
|
// src/commands/adminRotate.ts
|
|
9659
9690
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
9660
|
-
import { copyFileSync, existsSync as existsSync16, readFileSync as
|
|
9661
|
-
import { join as
|
|
9691
|
+
import { copyFileSync, existsSync as existsSync16, readFileSync as readFileSync16, writeFileSync as writeFileSync13 } from "fs";
|
|
9692
|
+
import { join as join13, relative as relative2 } from "path";
|
|
9662
9693
|
var NAME_PATTERN2 = /^[A-Za-z0-9_.-]+$/;
|
|
9663
9694
|
var FINGERPRINT_PATTERN2 = /^sha256:[0-9a-f]{64}$/;
|
|
9664
9695
|
var KNOWN_CAPABILITIES2 = ["admin", "operator", "server"];
|
|
9665
9696
|
function runAdminAddKey(opts) {
|
|
9666
9697
|
const repoRoot = findRepoRoot();
|
|
9667
|
-
const manifestPath =
|
|
9698
|
+
const manifestPath = join13(repoRoot, MANIFEST_RELATIVE_PATH);
|
|
9668
9699
|
const trustedDir = stampTrustedKeysDir(repoRoot);
|
|
9669
9700
|
if (!NAME_PATTERN2.test(opts.name)) {
|
|
9670
9701
|
throw new Error(
|
|
@@ -9675,7 +9706,7 @@ function runAdminAddKey(opts) {
|
|
|
9675
9706
|
if (!existsSync16(opts.pubkeyPath)) {
|
|
9676
9707
|
throw new Error(`pubkey file not found: ${opts.pubkeyPath}`);
|
|
9677
9708
|
}
|
|
9678
|
-
const pem =
|
|
9709
|
+
const pem = readFileSync16(opts.pubkeyPath, "utf8");
|
|
9679
9710
|
if (!pem.includes("-----BEGIN PUBLIC KEY-----")) {
|
|
9680
9711
|
throw new Error(
|
|
9681
9712
|
`${opts.pubkeyPath} does not look like a public key PEM (no "-----BEGIN PUBLIC KEY-----" header). Refusing to import \u2014 did you accidentally pass the private key path? Public keys live at ~/.stamp/keys/ed25519.pub (note the .pub suffix).`
|
|
@@ -9694,7 +9725,7 @@ function runAdminAddKey(opts) {
|
|
|
9694
9725
|
`no manifest at ${MANIFEST_RELATIVE_PATH}. Run \`stamp init --migrate-to-server-attested\` to bootstrap one, then re-run \`stamp admin add-key\`.`
|
|
9695
9726
|
);
|
|
9696
9727
|
}
|
|
9697
|
-
const existing = parseManifest(
|
|
9728
|
+
const existing = parseManifest(readFileSync16(manifestPath, "utf8"));
|
|
9698
9729
|
if (!existing) {
|
|
9699
9730
|
throw new Error(
|
|
9700
9731
|
`${MANIFEST_RELATIVE_PATH} failed to parse. Fix the file (see \`stamp admin list-keys\` for the current state) before adding a new key.`
|
|
@@ -9729,7 +9760,7 @@ function runAdminAddKey(opts) {
|
|
|
9729
9760
|
}
|
|
9730
9761
|
writeFileSync13(manifestPath, yamlText);
|
|
9731
9762
|
const pubDestFilename = publicKeyFingerprintFilename(fingerprint);
|
|
9732
|
-
const pubDestPath =
|
|
9763
|
+
const pubDestPath = join13(trustedDir, pubDestFilename);
|
|
9733
9764
|
if (!existsSync16(pubDestPath)) {
|
|
9734
9765
|
copyFileSync(opts.pubkeyPath, pubDestPath);
|
|
9735
9766
|
}
|
|
@@ -9754,7 +9785,7 @@ function runAdminAddKey(opts) {
|
|
|
9754
9785
|
}
|
|
9755
9786
|
function runAdminRevoke(opts) {
|
|
9756
9787
|
const repoRoot = findRepoRoot();
|
|
9757
|
-
const manifestPath =
|
|
9788
|
+
const manifestPath = join13(repoRoot, MANIFEST_RELATIVE_PATH);
|
|
9758
9789
|
if (!FINGERPRINT_PATTERN2.test(opts.fingerprint)) {
|
|
9759
9790
|
throw new Error(
|
|
9760
9791
|
`fingerprint ${JSON.stringify(opts.fingerprint)} is not in the expected sha256:<64-hex> form. Run \`stamp admin list-keys\` to copy a fingerprint exactly.`
|
|
@@ -9765,7 +9796,7 @@ function runAdminRevoke(opts) {
|
|
|
9765
9796
|
`no manifest at ${MANIFEST_RELATIVE_PATH} \u2014 nothing to revoke.`
|
|
9766
9797
|
);
|
|
9767
9798
|
}
|
|
9768
|
-
const existing = parseManifest(
|
|
9799
|
+
const existing = parseManifest(readFileSync16(manifestPath, "utf8"));
|
|
9769
9800
|
if (!existing) {
|
|
9770
9801
|
throw new Error(
|
|
9771
9802
|
`${MANIFEST_RELATIVE_PATH} failed to parse. Fix the file before revoking.`
|
|
@@ -9826,7 +9857,7 @@ function runAdminRevoke(opts) {
|
|
|
9826
9857
|
}
|
|
9827
9858
|
function runAdminListKeys(opts = {}) {
|
|
9828
9859
|
const repoRoot = findRepoRoot();
|
|
9829
|
-
const manifestPath =
|
|
9860
|
+
const manifestPath = join13(repoRoot, MANIFEST_RELATIVE_PATH);
|
|
9830
9861
|
if (!existsSync16(manifestPath)) {
|
|
9831
9862
|
if (opts.json) {
|
|
9832
9863
|
process.stdout.write(JSON.stringify({ entries: [] }, null, 2) + "\n");
|
|
@@ -9838,7 +9869,7 @@ function runAdminListKeys(opts = {}) {
|
|
|
9838
9869
|
);
|
|
9839
9870
|
return;
|
|
9840
9871
|
}
|
|
9841
|
-
const manifest = parseManifest(
|
|
9872
|
+
const manifest = parseManifest(readFileSync16(manifestPath, "utf8"));
|
|
9842
9873
|
if (!manifest) {
|
|
9843
9874
|
throw new Error(
|
|
9844
9875
|
`${MANIFEST_RELATIVE_PATH} failed to parse. Fix the file by hand (see \`git show HEAD:${MANIFEST_RELATIVE_PATH}\` for the last good version) before continuing.`
|
|
@@ -10613,7 +10644,7 @@ function readReviewerSource2(reviewerName, repoRoot) {
|
|
|
10613
10644
|
|
|
10614
10645
|
// src/commands/prune.ts
|
|
10615
10646
|
import { existsSync as existsSync18, readdirSync as readdirSync5, statSync as statSync3, unlinkSync as unlinkSync3 } from "fs";
|
|
10616
|
-
import { join as
|
|
10647
|
+
import { join as join14 } from "path";
|
|
10617
10648
|
|
|
10618
10649
|
// src/lib/duration.ts
|
|
10619
10650
|
function parseRetentionDuration(input) {
|
|
@@ -10642,8 +10673,8 @@ function runPrune(opts) {
|
|
|
10642
10673
|
);
|
|
10643
10674
|
const repoRoot = findRepoRoot();
|
|
10644
10675
|
const dbPath = stampStateDbPath(repoRoot);
|
|
10645
|
-
const parsesDir =
|
|
10646
|
-
const runsDir =
|
|
10676
|
+
const parsesDir = join14(gitCommonDir(repoRoot), "stamp", "failed-parses");
|
|
10677
|
+
const runsDir = join14(gitCommonDir(repoRoot), "stamp", "failed-runs");
|
|
10647
10678
|
const spoolCutoffMs = Date.now() - durationMs;
|
|
10648
10679
|
if (!existsSync18(dbPath) && !existsSync18(parsesDir) && !existsSync18(runsDir)) {
|
|
10649
10680
|
console.log(
|
|
@@ -10724,7 +10755,7 @@ function peekSpools(spoolDir, cutoffMs) {
|
|
|
10724
10755
|
if (!existsSync18(spoolDir)) return [];
|
|
10725
10756
|
const out = [];
|
|
10726
10757
|
for (const entry of readdirSync5(spoolDir)) {
|
|
10727
|
-
const filepath =
|
|
10758
|
+
const filepath = join14(spoolDir, entry);
|
|
10728
10759
|
let stat;
|
|
10729
10760
|
try {
|
|
10730
10761
|
stat = statSync3(filepath);
|
|
@@ -10879,27 +10910,27 @@ function loadOrEmpty() {
|
|
|
10879
10910
|
import { spawnSync as spawnSync15 } from "child_process";
|
|
10880
10911
|
import {
|
|
10881
10912
|
existsSync as existsSync21,
|
|
10882
|
-
readFileSync as
|
|
10913
|
+
readFileSync as readFileSync18,
|
|
10883
10914
|
statSync as statSync4,
|
|
10884
10915
|
unlinkSync as unlinkSync4,
|
|
10885
10916
|
writeFileSync as writeFileSync15
|
|
10886
10917
|
} from "fs";
|
|
10887
|
-
import { join as
|
|
10918
|
+
import { join as join16, relative as relative3, resolve as resolve2 } from "path";
|
|
10888
10919
|
import { parse as parseYaml10, stringify as stringifyYaml3 } from "yaml";
|
|
10889
10920
|
|
|
10890
10921
|
// src/lib/reviewerLock.ts
|
|
10891
|
-
import { existsSync as existsSync20, readFileSync as
|
|
10892
|
-
import { join as
|
|
10922
|
+
import { existsSync as existsSync20, readFileSync as readFileSync17, writeFileSync as writeFileSync14 } from "fs";
|
|
10923
|
+
import { join as join15 } from "path";
|
|
10893
10924
|
var LOCK_FILE_VERSION = 1;
|
|
10894
10925
|
var LOCK_DRIFT_EXIT = 3;
|
|
10895
10926
|
function lockFilePath(repoRoot, reviewerName) {
|
|
10896
|
-
return
|
|
10927
|
+
return join15(repoRoot, ".stamp", "reviewers", `${reviewerName}.lock.json`);
|
|
10897
10928
|
}
|
|
10898
10929
|
function readLockFile(repoRoot, reviewerName) {
|
|
10899
10930
|
const path2 = lockFilePath(repoRoot, reviewerName);
|
|
10900
10931
|
if (!existsSync20(path2)) return null;
|
|
10901
10932
|
try {
|
|
10902
|
-
const raw =
|
|
10933
|
+
const raw = readFileSync17(path2, "utf8");
|
|
10903
10934
|
const parsed = JSON.parse(raw);
|
|
10904
10935
|
if (typeof parsed.version !== "number" || typeof parsed.source !== "string" || typeof parsed.ref !== "string" || typeof parsed.reviewer !== "string" || typeof parsed.prompt_sha256 !== "string" || typeof parsed.tools_sha256 !== "string" || typeof parsed.mcp_sha256 !== "string") {
|
|
10905
10936
|
throw new Error(`malformed lock file at ${path2}`);
|
|
@@ -10920,13 +10951,13 @@ function checkReviewerDrift(repoRoot, reviewerName, def) {
|
|
|
10920
10951
|
if (!lock) {
|
|
10921
10952
|
return unpinnedResult();
|
|
10922
10953
|
}
|
|
10923
|
-
const promptPath =
|
|
10954
|
+
const promptPath = join15(repoRoot, def.prompt);
|
|
10924
10955
|
if (!existsSync20(promptPath)) {
|
|
10925
10956
|
throw new Error(
|
|
10926
10957
|
`reviewer "${reviewerName}" has a lock file but its prompt "${def.prompt}" does not exist on disk. Re-run 'stamp reviewers fetch ${reviewerName} --from ${lock.source}@${lock.ref}' to restore it, or delete the lock file to un-pin the reviewer.`
|
|
10927
10958
|
);
|
|
10928
10959
|
}
|
|
10929
|
-
const promptBytes =
|
|
10960
|
+
const promptBytes = readFileSync17(promptPath);
|
|
10930
10961
|
const observedPrompt = hashPromptBytes(promptBytes);
|
|
10931
10962
|
const observedTools = hashTools(def.tools);
|
|
10932
10963
|
const observedMcp = hashMcpServers(def.mcp_servers);
|
|
@@ -11128,8 +11159,8 @@ async function reviewersTest(name, diff) {
|
|
|
11128
11159
|
console.log(` prompt sourced from working tree (test/iteration use case)`);
|
|
11129
11160
|
console.log();
|
|
11130
11161
|
const def = config2.reviewers[name];
|
|
11131
|
-
const promptPath =
|
|
11132
|
-
const systemPrompt =
|
|
11162
|
+
const promptPath = join16(repoRoot, def.prompt);
|
|
11163
|
+
const systemPrompt = readFileSync18(promptPath, "utf8");
|
|
11133
11164
|
const result = await invokeReviewer({
|
|
11134
11165
|
reviewer: name,
|
|
11135
11166
|
config: config2,
|
|
@@ -11267,7 +11298,7 @@ async function reviewersFetch(reviewerName, opts) {
|
|
|
11267
11298
|
mcpServers = validateMcpServersFromSource(parsed.mcp_servers, source, ref);
|
|
11268
11299
|
}
|
|
11269
11300
|
}
|
|
11270
|
-
const promptPath =
|
|
11301
|
+
const promptPath = join16(reviewersDir, `${reviewerName}.md`);
|
|
11271
11302
|
const promptBytes = Buffer.from(promptText, "utf8");
|
|
11272
11303
|
const promptSha = hashPromptBytes(promptBytes);
|
|
11273
11304
|
const toolsSha = hashTools(tools);
|
|
@@ -11605,28 +11636,6 @@ function printGate(result, base_sha, head_sha) {
|
|
|
11605
11636
|
|
|
11606
11637
|
// src/commands/update.ts
|
|
11607
11638
|
import { spawnSync as spawnSync16 } from "child_process";
|
|
11608
|
-
|
|
11609
|
-
// src/lib/version.ts
|
|
11610
|
-
import { readFileSync as readFileSync18 } from "fs";
|
|
11611
|
-
import { dirname as dirname7, join as join16 } from "path";
|
|
11612
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
11613
|
-
function readPackageVersion() {
|
|
11614
|
-
const here = dirname7(fileURLToPath2(import.meta.url));
|
|
11615
|
-
for (let dir = here, i = 0; i < 6; i++) {
|
|
11616
|
-
try {
|
|
11617
|
-
const raw = readFileSync18(join16(dir, "package.json"), "utf8");
|
|
11618
|
-
const pkg = JSON.parse(raw);
|
|
11619
|
-
if (pkg.name === "@openthink/stamp" && pkg.version) return pkg.version;
|
|
11620
|
-
} catch {
|
|
11621
|
-
}
|
|
11622
|
-
const parent = dirname7(dir);
|
|
11623
|
-
if (parent === dir) break;
|
|
11624
|
-
dir = parent;
|
|
11625
|
-
}
|
|
11626
|
-
throw new Error("could not locate @openthink/stamp package.json to read version");
|
|
11627
|
-
}
|
|
11628
|
-
|
|
11629
|
-
// src/commands/update.ts
|
|
11630
11639
|
var PKG_NAME = "@openthink/stamp";
|
|
11631
11640
|
function compareSemver(a, b) {
|
|
11632
11641
|
const parse2 = (v) => (v.split("-")[0] ?? "0.0.0").split(".").map((n) => parseInt(n, 10) || 0);
|
|
@@ -12228,6 +12237,9 @@ program.command("init").description(
|
|
|
12228
12237
|
).option(
|
|
12229
12238
|
"--pr-mode-force",
|
|
12230
12239
|
"with --pr-mode, overwrite an existing .github/workflows/stamp-mirror.yml (useful when re-running after configuring review_server so the host/port placeholders fill in)"
|
|
12240
|
+
).option(
|
|
12241
|
+
"--action-source <org/repo>",
|
|
12242
|
+
"GitHub repo that hosts the stamp/verify-attestation action used by .github/workflows/stamp-verify.yml. Default 'OpenThinkAi/stamp-cli'. Override when consuming a fork (e.g. 'Anglepoint-Inc/anglepoint-stamp-server') so the workflow tracks your fork's updates instead of the upstream."
|
|
12231
12243
|
).action(
|
|
12232
12244
|
(opts) => {
|
|
12233
12245
|
try {
|
|
@@ -12235,6 +12247,11 @@ program.command("init").description(
|
|
|
12235
12247
|
runMigrateToServerAttested({ dryRun: opts.dryRun === true });
|
|
12236
12248
|
return;
|
|
12237
12249
|
}
|
|
12250
|
+
if (opts.dryRun === true) {
|
|
12251
|
+
throw new Error(
|
|
12252
|
+
"--dry-run is supported only with --migrate-to-server-attested. Re-run with --migrate-to-server-attested to preview the migration scaffold, or drop --dry-run to run the requested init operation."
|
|
12253
|
+
);
|
|
12254
|
+
}
|
|
12238
12255
|
let mode;
|
|
12239
12256
|
if (opts.mode === void 0) {
|
|
12240
12257
|
mode = void 0;
|
|
@@ -12245,6 +12262,13 @@ program.command("init").description(
|
|
|
12245
12262
|
`--mode must be 'server-gated' or 'local-only' (got "${opts.mode}")`
|
|
12246
12263
|
);
|
|
12247
12264
|
}
|
|
12265
|
+
if (opts.actionSource !== void 0) {
|
|
12266
|
+
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(opts.actionSource)) {
|
|
12267
|
+
throw new Error(
|
|
12268
|
+
`--action-source must be of the form 'org/repo' (got "${opts.actionSource}")`
|
|
12269
|
+
);
|
|
12270
|
+
}
|
|
12271
|
+
}
|
|
12248
12272
|
runInit({
|
|
12249
12273
|
minimal: opts.minimal,
|
|
12250
12274
|
agentsMd: opts.agentsMd,
|
|
@@ -12260,7 +12284,8 @@ program.command("init").description(
|
|
|
12260
12284
|
// default fires when the operator hasn't opted out.
|
|
12261
12285
|
prCheck: opts.prCheck === false ? false : void 0,
|
|
12262
12286
|
prMode: opts.prMode === true,
|
|
12263
|
-
prModeForce: opts.prModeForce === true
|
|
12287
|
+
prModeForce: opts.prModeForce === true,
|
|
12288
|
+
actionSource: opts.actionSource
|
|
12264
12289
|
});
|
|
12265
12290
|
} catch (err) {
|
|
12266
12291
|
const message = err instanceof Error ? err.message : String(err);
|