halfcycle 0.4.8 → 0.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/bin/bin.bundle.mjs +2162 -335
- package/commands/agent-teams-orchestration.md +1 -1
- package/commands/consistency-check.md +1 -1
- package/commands/context-update.md +1 -1
- package/commands/halfcycle-design.md +1 -1
- package/commands/halfcycle-setup.md +1 -1
- package/commands/stack-assembly-worker.md +1 -1
- package/commands/task-execution.md +1 -1
- package/commands/task-graph.md +1 -1
- package/commands/write-spec.md +1 -1
- package/dist/bin.d.ts +2 -1
- package/dist/bin.d.ts.map +1 -1
- package/dist/bin.js +0 -0
- package/dist/build-record/obtain.d.ts +14 -7
- package/dist/build-record/obtain.d.ts.map +1 -1
- package/dist/build-record/render-v2.d.ts +32 -6
- package/dist/build-record/render-v2.d.ts.map +1 -1
- package/dist/build-record/write.d.ts +2 -2
- package/dist/build-record/write.d.ts.map +1 -1
- package/dist/cli-contract.d.ts +2 -9
- package/dist/cli-contract.d.ts.map +1 -1
- package/dist/cli.js +3859 -1946
- package/dist/cli.js.map +3 -3
- package/dist/close-phase.d.ts +223 -134
- package/dist/close-phase.d.ts.map +1 -1
- package/dist/draw/index.d.ts +29 -6
- package/dist/draw/index.d.ts.map +1 -1
- package/dist/identity.d.ts +78 -9
- package/dist/identity.d.ts.map +1 -1
- package/dist/index.js +1685 -232
- package/dist/index.js.map +3 -3
- package/dist/install.d.ts +16 -0
- package/dist/install.d.ts.map +1 -1
- package/dist/open-phase.d.ts +25 -1
- package/dist/open-phase.d.ts.map +1 -1
- package/dist/plan.d.ts +75 -23
- package/dist/plan.d.ts.map +1 -1
- package/dist/pre-create-checks.d.ts.map +1 -1
- package/dist/review.d.ts +231 -0
- package/dist/review.d.ts.map +1 -0
- package/dist/shown-record.d.ts +46 -0
- package/dist/shown-record.d.ts.map +1 -0
- package/dist/system-report.d.ts +28 -4
- package/dist/system-report.d.ts.map +1 -1
- package/package.json +11 -11
- package/dist/project-page.d.ts +0 -24
- package/dist/project-page.d.ts.map +0 -1
package/bin/bin.bundle.mjs
CHANGED
|
@@ -14781,32 +14781,68 @@ var HALFCYCLE_COMMAND = "npx halfcycle@latest";
|
|
|
14781
14781
|
|
|
14782
14782
|
// ../core/dist/shown-plans.js
|
|
14783
14783
|
var SHOWN_PLANS_FILENAME = "shown-plans.json";
|
|
14784
|
-
var
|
|
14785
|
-
var
|
|
14784
|
+
var SHOWN_RECORD_FORMAT = 2;
|
|
14785
|
+
var SHOWN_RECORD_FORMAT_1 = 1;
|
|
14786
|
+
var FORMAT_1_KEYS = ["entries", "format"];
|
|
14787
|
+
var RECORD_KEYS = ["entries", "format", "reviews"];
|
|
14788
|
+
var WRITTEN_KEYS = ["entries", "reviews"];
|
|
14786
14789
|
var ENTRY_KEYS = ["number", "phase", "shownAt", "version"];
|
|
14787
|
-
|
|
14790
|
+
var REVIEW_KEYS = ["number", "parts", "phase", "reviewVersion", "round", "shownAt"];
|
|
14791
|
+
var REVIEW_PART_KEYS = ["awaitingAnswer", "built", "decidable", "name", "part", "sentBack"];
|
|
14792
|
+
function nothingShown() {
|
|
14793
|
+
return { entries: [], reviews: [] };
|
|
14794
|
+
}
|
|
14795
|
+
function parseShownRecord(text) {
|
|
14788
14796
|
try {
|
|
14789
14797
|
if (typeof text !== "string")
|
|
14790
|
-
return
|
|
14798
|
+
return nothingShown();
|
|
14791
14799
|
const parsed = JSON.parse(text);
|
|
14792
|
-
if (!isPlainObject2(parsed)
|
|
14793
|
-
return
|
|
14794
|
-
|
|
14795
|
-
|
|
14796
|
-
|
|
14797
|
-
if (
|
|
14798
|
-
|
|
14799
|
-
|
|
14800
|
-
|
|
14801
|
-
|
|
14802
|
-
|
|
14803
|
-
|
|
14804
|
-
entries.push(entry);
|
|
14805
|
-
}
|
|
14806
|
-
return hasDuplicates(entries) ? [] : entries;
|
|
14800
|
+
if (!isPlainObject2(parsed))
|
|
14801
|
+
return nothingShown();
|
|
14802
|
+
let rawReviews;
|
|
14803
|
+
if (parsed["format"] === SHOWN_RECORD_FORMAT && hasExactKeys(parsed, RECORD_KEYS)) {
|
|
14804
|
+
rawReviews = parsed["reviews"];
|
|
14805
|
+
} else if (parsed["format"] === SHOWN_RECORD_FORMAT_1 && hasExactKeys(parsed, FORMAT_1_KEYS)) {
|
|
14806
|
+
rawReviews = [];
|
|
14807
|
+
} else {
|
|
14808
|
+
return nothingShown();
|
|
14809
|
+
}
|
|
14810
|
+
const record2 = readRecord(parsed["entries"], rawReviews);
|
|
14811
|
+
return record2 ?? nothingShown();
|
|
14807
14812
|
} catch {
|
|
14808
|
-
return
|
|
14813
|
+
return nothingShown();
|
|
14814
|
+
}
|
|
14815
|
+
}
|
|
14816
|
+
function serialiseShownRecord(record2) {
|
|
14817
|
+
if (!isPlainObject2(record2) || !hasExactKeys(record2, WRITTEN_KEYS))
|
|
14818
|
+
throw new Error(UNRECORDABLE);
|
|
14819
|
+
const copied = readRecord(record2["entries"], record2["reviews"]);
|
|
14820
|
+
if (copied === null)
|
|
14821
|
+
throw new Error(UNRECORDABLE);
|
|
14822
|
+
return `${JSON.stringify({ format: SHOWN_RECORD_FORMAT, entries: copied.entries, reviews: copied.reviews }, null, 2)}
|
|
14823
|
+
`;
|
|
14824
|
+
}
|
|
14825
|
+
var UNRECORDABLE = "the list just shown could not be recorded on this machine.";
|
|
14826
|
+
function readRecord(rawEntries, rawReviews) {
|
|
14827
|
+
if (!Array.isArray(rawEntries) || !Array.isArray(rawReviews))
|
|
14828
|
+
return null;
|
|
14829
|
+
const entries = [];
|
|
14830
|
+
for (const raw of rawEntries) {
|
|
14831
|
+
const entry = readEntry(raw);
|
|
14832
|
+
if (entry === null)
|
|
14833
|
+
return null;
|
|
14834
|
+
entries.push(entry);
|
|
14835
|
+
}
|
|
14836
|
+
const reviews = [];
|
|
14837
|
+
for (const raw of rawReviews) {
|
|
14838
|
+
const review = readReview(raw);
|
|
14839
|
+
if (review === null)
|
|
14840
|
+
return null;
|
|
14841
|
+
reviews.push(review);
|
|
14809
14842
|
}
|
|
14843
|
+
if (hasDuplicates(entries) || hasDuplicates(reviews))
|
|
14844
|
+
return null;
|
|
14845
|
+
return { entries, reviews };
|
|
14810
14846
|
}
|
|
14811
14847
|
function readEntry(raw) {
|
|
14812
14848
|
if (!isPlainObject2(raw) || !hasExactKeys(raw, ENTRY_KEYS))
|
|
@@ -14815,7 +14851,7 @@ function readEntry(raw) {
|
|
|
14815
14851
|
const number4 = raw["number"];
|
|
14816
14852
|
const version2 = raw["version"];
|
|
14817
14853
|
const shownAt = raw["shownAt"];
|
|
14818
|
-
if (
|
|
14854
|
+
if (!isNonEmptyString(phase))
|
|
14819
14855
|
return null;
|
|
14820
14856
|
if (!isPositiveInteger(number4) || !isPositiveInteger(version2))
|
|
14821
14857
|
return null;
|
|
@@ -14823,6 +14859,53 @@ function readEntry(raw) {
|
|
|
14823
14859
|
return null;
|
|
14824
14860
|
return { phase, number: number4, version: version2, shownAt };
|
|
14825
14861
|
}
|
|
14862
|
+
function readReview(raw) {
|
|
14863
|
+
if (!isPlainObject2(raw) || !hasExactKeys(raw, REVIEW_KEYS))
|
|
14864
|
+
return null;
|
|
14865
|
+
const phase = raw["phase"];
|
|
14866
|
+
const number4 = raw["number"];
|
|
14867
|
+
const round = raw["round"];
|
|
14868
|
+
const reviewVersion = raw["reviewVersion"];
|
|
14869
|
+
const shownAt = raw["shownAt"];
|
|
14870
|
+
const rawParts = raw["parts"];
|
|
14871
|
+
if (!isNonEmptyString(phase))
|
|
14872
|
+
return null;
|
|
14873
|
+
if (number4 !== null && !isPositiveInteger(number4))
|
|
14874
|
+
return null;
|
|
14875
|
+
if (!isPositiveInteger(round) || !isWholeNumber(reviewVersion))
|
|
14876
|
+
return null;
|
|
14877
|
+
if (!isIsoInstant(shownAt))
|
|
14878
|
+
return null;
|
|
14879
|
+
if (!Array.isArray(rawParts))
|
|
14880
|
+
return null;
|
|
14881
|
+
const parts = [];
|
|
14882
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14883
|
+
for (const rawPart of rawParts) {
|
|
14884
|
+
const part = readReviewPart(rawPart);
|
|
14885
|
+
if (part === null || seen.has(part.part))
|
|
14886
|
+
return null;
|
|
14887
|
+
seen.add(part.part);
|
|
14888
|
+
parts.push(part);
|
|
14889
|
+
}
|
|
14890
|
+
return { phase, number: number4, round, reviewVersion, shownAt, parts };
|
|
14891
|
+
}
|
|
14892
|
+
function readReviewPart(raw) {
|
|
14893
|
+
if (!isPlainObject2(raw) || !hasExactKeys(raw, REVIEW_PART_KEYS))
|
|
14894
|
+
return null;
|
|
14895
|
+
const part = raw["part"];
|
|
14896
|
+
const name = raw["name"];
|
|
14897
|
+
const built = raw["built"];
|
|
14898
|
+
const decidable = raw["decidable"];
|
|
14899
|
+
const awaitingAnswer = raw["awaitingAnswer"];
|
|
14900
|
+
const sentBack = raw["sentBack"];
|
|
14901
|
+
if (!isNonEmptyString(part) || !isNonEmptyString(name))
|
|
14902
|
+
return null;
|
|
14903
|
+
if (typeof built !== "boolean" || typeof decidable !== "boolean")
|
|
14904
|
+
return null;
|
|
14905
|
+
if (typeof awaitingAnswer !== "boolean" || typeof sentBack !== "boolean")
|
|
14906
|
+
return null;
|
|
14907
|
+
return { part, name, built, decidable, awaitingAnswer, sentBack };
|
|
14908
|
+
}
|
|
14826
14909
|
function isPlainObject2(value) {
|
|
14827
14910
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
14828
14911
|
return false;
|
|
@@ -14833,19 +14916,26 @@ function hasExactKeys(value, keys) {
|
|
|
14833
14916
|
const own = Object.keys(value).sort();
|
|
14834
14917
|
return own.length === keys.length && own.every((key, i) => key === keys[i]);
|
|
14835
14918
|
}
|
|
14919
|
+
function isNonEmptyString(value) {
|
|
14920
|
+
return typeof value === "string" && value !== "";
|
|
14921
|
+
}
|
|
14836
14922
|
function isPositiveInteger(value) {
|
|
14837
14923
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
|
|
14838
14924
|
}
|
|
14925
|
+
function isWholeNumber(value) {
|
|
14926
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
14927
|
+
}
|
|
14839
14928
|
function isIsoInstant(value) {
|
|
14840
14929
|
if (typeof value !== "string")
|
|
14841
14930
|
return false;
|
|
14842
14931
|
const ms = Date.parse(value);
|
|
14843
14932
|
return Number.isFinite(ms) && new Date(ms).toISOString() === value;
|
|
14844
14933
|
}
|
|
14845
|
-
function hasDuplicates(
|
|
14846
|
-
const phases = new Set(
|
|
14847
|
-
const
|
|
14848
|
-
|
|
14934
|
+
function hasDuplicates(items) {
|
|
14935
|
+
const phases = new Set(items.map((item) => item.phase));
|
|
14936
|
+
const numbered = items.filter((item) => item.number !== null);
|
|
14937
|
+
const numbers = new Set(numbered.map((item) => item.number));
|
|
14938
|
+
return phases.size !== items.length || numbers.size !== numbered.length;
|
|
14849
14939
|
}
|
|
14850
14940
|
|
|
14851
14941
|
// ../core/dist/phase-reference.js
|
|
@@ -14861,10 +14951,54 @@ function resolvePhaseReference(typed, phases) {
|
|
|
14861
14951
|
const byNumber = phases.filter((entry) => Number.isInteger(entry.number) && String(entry.number) === typed);
|
|
14862
14952
|
return byNumber.length === 1 ? byNumber[0] : null;
|
|
14863
14953
|
}
|
|
14954
|
+
function typedPhaseReference(phase, number4) {
|
|
14955
|
+
return phase.includes(":") && number4 !== null ? String(number4) : phase;
|
|
14956
|
+
}
|
|
14864
14957
|
|
|
14865
14958
|
// ../core/dist/release-version.js
|
|
14866
14959
|
var RELEASE_VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
14867
14960
|
|
|
14961
|
+
// ../core/dist/github-address.js
|
|
14962
|
+
var GITHUB_REPOSITORY_ADDRESS = /^https:\/\/github\.com\/[A-Za-z0-9][A-Za-z0-9-]{0,38}\/(?!\.{1,2}$)[A-Za-z0-9._-]{1,100}$/;
|
|
14963
|
+
var HOST = "[Gg][Ii][Tt][Hh][Uu][Bb]\\.[Cc][Oo][Mm]";
|
|
14964
|
+
var OWNER = "[A-Za-z0-9][A-Za-z0-9-]{0,38}";
|
|
14965
|
+
var RAW_REPOSITORY = "[A-Za-z0-9._-]{1,104}";
|
|
14966
|
+
var TAIL = `(${OWNER})/(${RAW_REPOSITORY})/?$`;
|
|
14967
|
+
var URL_USERINFO = "(?:[^\\s/?#@]+@)?";
|
|
14968
|
+
var SSH_USER = "(?:[^\\s/?#@:]+@)?";
|
|
14969
|
+
var REMOTE_FORMS = [
|
|
14970
|
+
new RegExp(`^https?://${URL_USERINFO}${HOST}/${TAIL}`),
|
|
14971
|
+
new RegExp(`^ssh://${SSH_USER}${HOST}/${TAIL}`),
|
|
14972
|
+
new RegExp(`^git://${HOST}/${TAIL}`),
|
|
14973
|
+
new RegExp(`^${SSH_USER}${HOST}:${TAIL}`)
|
|
14974
|
+
];
|
|
14975
|
+
var REPOSITORY_NAME = /^(?!\.{1,2}$)[A-Za-z0-9._-]{1,100}$/;
|
|
14976
|
+
function githubRepositoryAddress(remote) {
|
|
14977
|
+
for (const form of REMOTE_FORMS) {
|
|
14978
|
+
const match = form.exec(remote);
|
|
14979
|
+
if (match === null)
|
|
14980
|
+
continue;
|
|
14981
|
+
const owner = match[1];
|
|
14982
|
+
const raw = match[2];
|
|
14983
|
+
if (owner === void 0 || raw === void 0)
|
|
14984
|
+
return null;
|
|
14985
|
+
const repository = raw.endsWith(".git") ? raw.slice(0, -4) : raw;
|
|
14986
|
+
if (!REPOSITORY_NAME.test(repository))
|
|
14987
|
+
return null;
|
|
14988
|
+
const address = `https://github.com/${owner}/${repository}`;
|
|
14989
|
+
if (!GITHUB_REPOSITORY_ADDRESS.test(address))
|
|
14990
|
+
return null;
|
|
14991
|
+
if (looksLikeSecret(address))
|
|
14992
|
+
return null;
|
|
14993
|
+
return address;
|
|
14994
|
+
}
|
|
14995
|
+
return null;
|
|
14996
|
+
}
|
|
14997
|
+
|
|
14998
|
+
// ../core/dist/sign-in.js
|
|
14999
|
+
var SIGN_IN_COMMAND = '"npx halfcycle"';
|
|
15000
|
+
var HOOK_REMEDY = `Run ${SIGN_IN_COMMAND} in the project folder to sign in.`;
|
|
15001
|
+
|
|
14868
15002
|
// dist/control-origin.js
|
|
14869
15003
|
var DEFAULT_CONTROL_ORIGIN = "https://control.halfcycle.ai";
|
|
14870
15004
|
function resolveControlOrigin(env) {
|
|
@@ -15150,6 +15284,11 @@ function hasLoneSurrogate(text) {
|
|
|
15150
15284
|
}
|
|
15151
15285
|
var LONE_SURROGATE_MESSAGE = "must be valid text: it holds half of a character pair with no other half";
|
|
15152
15286
|
|
|
15287
|
+
// ../events/dist/human-agent.js
|
|
15288
|
+
var HUMAN_AGENT = "Human";
|
|
15289
|
+
var AGENT_NAME_MAX = 32;
|
|
15290
|
+
var agentNameSchema = external_exports.string().min(1).max(AGENT_NAME_MAX);
|
|
15291
|
+
|
|
15153
15292
|
// ../events/dist/state.js
|
|
15154
15293
|
var utcIso8601 = external_exports.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/, "must be a UTC ISO-8601 timestamp ending in Z");
|
|
15155
15294
|
var STATE_RECORD_FORMAT = "halfcycle-state-record/v1";
|
|
@@ -15273,6 +15412,7 @@ var coverageSchema = external_exports.object({
|
|
|
15273
15412
|
});
|
|
15274
15413
|
}
|
|
15275
15414
|
});
|
|
15415
|
+
var projectTierSchema = external_exports.number().int().min(0).max(3);
|
|
15276
15416
|
var artefactRecordSchema = stateRecordEnvelopeSchema.extend({
|
|
15277
15417
|
recordKind: external_exports.literal("artefact"),
|
|
15278
15418
|
artefactRef: artefactRefSchema,
|
|
@@ -15285,7 +15425,13 @@ var stepRunRecordSchema = stateRecordEnvelopeSchema.extend({
|
|
|
15285
15425
|
recordKind: external_exports.literal("step-run"),
|
|
15286
15426
|
runOutcome: runOutcomeSchema,
|
|
15287
15427
|
failureReason: external_exports.string().optional(),
|
|
15288
|
-
outcomeReason: external_exports.string().max(2e3).optional()
|
|
15428
|
+
outcomeReason: external_exports.string().max(2e3).optional(),
|
|
15429
|
+
// Who did this step, as the session reported it. Absent when not reported.
|
|
15430
|
+
agent: agentNameSchema.optional(),
|
|
15431
|
+
// When the session began this step. Absent when not reported.
|
|
15432
|
+
startedAt: utcIso8601.optional(),
|
|
15433
|
+
// The project's tier, when this step settled it. Absent on every other step.
|
|
15434
|
+
projectTier: projectTierSchema.optional()
|
|
15289
15435
|
}).strict().superRefine((rec, ctx) => {
|
|
15290
15436
|
if (rec.runOutcome === "attempted-failed" && rec.failureReason === void 0) {
|
|
15291
15437
|
ctx.addIssue({
|
|
@@ -15312,7 +15458,12 @@ var gateRecordSchema = stateRecordEnvelopeSchema.extend({
|
|
|
15312
15458
|
verdict: gateVerdictSchema,
|
|
15313
15459
|
actor: external_exports.string(),
|
|
15314
15460
|
outcomeReason: external_exports.string().max(2e3).optional(),
|
|
15315
|
-
coverage: coverageSchema.optional()
|
|
15461
|
+
coverage: coverageSchema.optional(),
|
|
15462
|
+
// Who did the step this checkpoint is on, as reported. Absent when not reported.
|
|
15463
|
+
agent: agentNameSchema.optional(),
|
|
15464
|
+
// When that step, or the review this checkpoint closes, began. Absent when not
|
|
15465
|
+
// reported.
|
|
15466
|
+
startedAt: utcIso8601.optional()
|
|
15316
15467
|
}).strict().superRefine((rec, ctx) => {
|
|
15317
15468
|
const issue2 = outcomeReasonIssue(rec.verdict, rec.outcomeReason);
|
|
15318
15469
|
if (issue2) {
|
|
@@ -15326,7 +15477,9 @@ var interventionRecordSchema = stateRecordEnvelopeSchema.extend({
|
|
|
15326
15477
|
severity: severitySchema,
|
|
15327
15478
|
disposition: dispositionSchema,
|
|
15328
15479
|
summary: external_exports.string().max(2e3),
|
|
15329
|
-
coverage: coverageSchema.optional()
|
|
15480
|
+
coverage: coverageSchema.optional(),
|
|
15481
|
+
// Who caught it, as the session reported. Absent when not reported.
|
|
15482
|
+
agent: agentNameSchema.optional()
|
|
15330
15483
|
}).strict();
|
|
15331
15484
|
var stateRecordSchema = external_exports.discriminatedUnion("recordKind", [
|
|
15332
15485
|
artefactRecordSchema,
|
|
@@ -15420,6 +15573,11 @@ var FINDINGS_SHOWN_MAX = 50;
|
|
|
15420
15573
|
var PHASE_CLOSE_REASON_MAX = 1900;
|
|
15421
15574
|
var closeCommitSchema = external_exports.string().regex(/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/, "must be a full commit hash: 40 or 64 lowercase hex characters");
|
|
15422
15575
|
var walkVerdictSchema = external_exports.enum(["clean", "defects"]);
|
|
15576
|
+
var acceptanceOutcomeSchema = external_exports.object({
|
|
15577
|
+
verdict: walkVerdictSchema,
|
|
15578
|
+
findings: external_exports.number().int().nonnegative(),
|
|
15579
|
+
findingsDetail: external_exports.array(external_exports.object({ summary: external_exports.string().refine((v) => !hasLoneSurrogate(v), { message: LONE_SURROGATE_MESSAGE }) }).strict())
|
|
15580
|
+
}).strict();
|
|
15423
15581
|
var closeDecisionSchema = external_exports.enum(["done-when-met", "override"]);
|
|
15424
15582
|
function plainText(max) {
|
|
15425
15583
|
const base = max === void 0 ? external_exports.string() : external_exports.string().max(max);
|
|
@@ -15543,12 +15701,7 @@ var attestedCloseSchema = external_exports.object({
|
|
|
15543
15701
|
var attestedSchema = external_exports.object({
|
|
15544
15702
|
closes: external_exports.array(attestedCloseSchema)
|
|
15545
15703
|
}).strict();
|
|
15546
|
-
|
|
15547
|
-
format: external_exports.literal(BUILD_RECORD_V2_FORMAT),
|
|
15548
|
-
phase: phaseNameSchema,
|
|
15549
|
-
observed: observedSchema,
|
|
15550
|
-
attested: attestedSchema
|
|
15551
|
-
}).strict().superRefine((record2, ctx) => {
|
|
15704
|
+
function checkRecordHalves(record2, ctx) {
|
|
15552
15705
|
const observedBySequence = new Map(record2.observed.closes.map((c) => [c.sequence, c]));
|
|
15553
15706
|
const attestedSequences = record2.attested.closes.map((c) => c.sequence);
|
|
15554
15707
|
const sameSet = attestedSequences.length === observedBySequence.size && new Set(attestedSequences).size === attestedSequences.length && attestedSequences.every((s) => observedBySequence.has(s));
|
|
@@ -15576,139 +15729,13 @@ var buildRecordV2Schema = external_exports.object({
|
|
|
15576
15729
|
});
|
|
15577
15730
|
}
|
|
15578
15731
|
});
|
|
15579
|
-
}
|
|
15580
|
-
|
|
15581
|
-
|
|
15582
|
-
|
|
15583
|
-
|
|
15584
|
-
|
|
15585
|
-
|
|
15586
|
-
"pending",
|
|
15587
|
-
"in-flight",
|
|
15588
|
-
"completed",
|
|
15589
|
-
"failed",
|
|
15590
|
-
"blocked"
|
|
15591
|
-
]);
|
|
15592
|
-
var BUILD_TASK_TITLE_MAX = 240;
|
|
15593
|
-
var BUILD_SNAPSHOT_TASK_LIMIT = 500;
|
|
15594
|
-
var buildTaskSchema = external_exports.object({
|
|
15595
|
-
// The task's own id, as the coordinator's graph names it.
|
|
15596
|
-
id: external_exports.string().min(1).max(64),
|
|
15597
|
-
// What the task is, in the coordinator's words. A description of the work,
|
|
15598
|
-
// bounded and scanned — never an excerpt of anybody's code.
|
|
15599
|
-
title: external_exports.string().min(1).max(BUILD_TASK_TITLE_MAX),
|
|
15600
|
-
// What the task is doing right now.
|
|
15601
|
-
state: buildTaskStateSchema,
|
|
15602
|
-
// Who holds it — a bare callsign, or the value naming a person when the work is
|
|
15603
|
-
// a person's. Absent when nothing has been dispatched to it yet.
|
|
15604
|
-
agent: external_exports.string().min(1).max(32).optional(),
|
|
15605
|
-
// The kind of work the holder does. Present exactly when an agent holds it.
|
|
15606
|
-
discipline: external_exports.string().min(1).max(64).optional(),
|
|
15607
|
-
// How much thinking the holder brings. Present exactly when an agent holds it,
|
|
15608
|
-
// and there is no field naming the supplier behind it.
|
|
15609
|
-
tier: external_exports.string().min(1).max(32).optional(),
|
|
15610
|
-
// The tasks this one cannot start before. Always present; empty means it waits
|
|
15611
|
-
// on nothing, which is different from not having been computed.
|
|
15612
|
-
dependsOn: external_exports.array(external_exports.string().min(1).max(64)).max(BUILD_SNAPSHOT_TASK_LIMIT),
|
|
15613
|
-
// The one task this task checks, when it is a check on other work. Absent on a
|
|
15614
|
-
// task that is doing the work rather than checking it.
|
|
15615
|
-
reviews: external_exports.string().min(1).max(64).optional(),
|
|
15616
|
-
// Which wave of the phase this task belongs to — the wave that writes the
|
|
15617
|
-
// specifications, or the wave that builds them. Absent when the graph does not
|
|
15618
|
-
// record it, which is different from the task belonging to neither.
|
|
15619
|
-
layer: methodLayerSchema.optional(),
|
|
15620
|
-
// When it was picked up, and when it settled. A reader computes how long it
|
|
15621
|
-
// took; neither this row nor this payload carries a duration.
|
|
15622
|
-
startedAt: utcIso8601.optional(),
|
|
15623
|
-
settledAt: utcIso8601.optional()
|
|
15624
|
-
}).strict().superRefine((task, ctx) => {
|
|
15625
|
-
if (task.reviews !== void 0 && task.reviews === task.id) {
|
|
15626
|
-
ctx.addIssue({
|
|
15627
|
-
code: external_exports.ZodIssueCode.custom,
|
|
15628
|
-
path: ["reviews"],
|
|
15629
|
-
message: `'${task.id}' reviews itself \u2014 a review names the OTHER task it checks.`
|
|
15630
|
-
});
|
|
15631
|
-
}
|
|
15632
|
-
if (task.layer !== void 0 && !isPhaseScopedLayer(task.layer)) {
|
|
15633
|
-
ctx.addIssue({
|
|
15634
|
-
code: external_exports.ZodIssueCode.custom,
|
|
15635
|
-
path: ["layer"],
|
|
15636
|
-
message: `'${task.layer}' is not a wave a phase runs \u2014 expected one of ${PHASE_SCOPED_LAYERS.join(", ")}.`
|
|
15637
|
-
});
|
|
15638
|
-
}
|
|
15639
|
-
const profiled = task.agent !== void 0 && task.agent !== HUMAN_AGENT;
|
|
15640
|
-
for (const field of ["discipline", "tier"]) {
|
|
15641
|
-
if (profiled && task[field] === void 0) {
|
|
15642
|
-
ctx.addIssue({
|
|
15643
|
-
code: external_exports.ZodIssueCode.custom,
|
|
15644
|
-
path: [field],
|
|
15645
|
-
message: `'${field}' is required on a task an agent holds.`
|
|
15646
|
-
});
|
|
15647
|
-
}
|
|
15648
|
-
if (!profiled && task[field] !== void 0) {
|
|
15649
|
-
ctx.addIssue({
|
|
15650
|
-
code: external_exports.ZodIssueCode.custom,
|
|
15651
|
-
path: [field],
|
|
15652
|
-
message: task.agent === void 0 ? `'${field}' must be absent on a task nobody holds.` : `'${field}' must be absent on work a person holds.`
|
|
15653
|
-
});
|
|
15654
|
-
}
|
|
15655
|
-
}
|
|
15656
|
-
});
|
|
15657
|
-
var buildPhaseSchema = external_exports.string().min(1).max(128);
|
|
15658
|
-
var BUILD_PHASE_TITLE_MAX = 160;
|
|
15659
|
-
var buildPhaseTitleSchema = external_exports.string().min(1).max(BUILD_PHASE_TITLE_MAX);
|
|
15660
|
-
var buildTaskListSchema = external_exports.array(buildTaskSchema).max(BUILD_SNAPSHOT_TASK_LIMIT).superRefine((tasks, ctx) => {
|
|
15661
|
-
const seen = /* @__PURE__ */ new Set();
|
|
15662
|
-
for (const [index, task] of tasks.entries()) {
|
|
15663
|
-
if (seen.has(task.id)) {
|
|
15664
|
-
ctx.addIssue({
|
|
15665
|
-
code: external_exports.ZodIssueCode.custom,
|
|
15666
|
-
path: [index, "id"],
|
|
15667
|
-
message: `duplicate task id '${task.id}' \u2014 one graph names each task once.`
|
|
15668
|
-
});
|
|
15669
|
-
}
|
|
15670
|
-
seen.add(task.id);
|
|
15671
|
-
}
|
|
15672
|
-
});
|
|
15673
|
-
var buildSnapshotSchema = external_exports.object({
|
|
15674
|
-
// Which phase this graph belongs to.
|
|
15675
|
-
phase: buildPhaseSchema,
|
|
15676
|
-
// That phase's name in words, when the project gives it one.
|
|
15677
|
-
phaseTitle: buildPhaseTitleSchema.optional(),
|
|
15678
|
-
// Every task on it. Never a partial graph and never a change since last time.
|
|
15679
|
-
tasks: buildTaskListSchema,
|
|
15680
|
-
// When this graph was last reported.
|
|
15681
|
-
reportedAt: utcIso8601,
|
|
15682
|
-
// When work on this phase was first reported with no approval of its scope in force.
|
|
15683
|
-
builtWithoutApproval: utcIso8601.optional()
|
|
15684
|
-
}).strict();
|
|
15685
|
-
var buildPhaseEntrySchema = external_exports.object({
|
|
15686
|
-
// Which phase has a stored graph.
|
|
15687
|
-
phase: buildPhaseSchema,
|
|
15688
|
-
// That phase's name in words, when the project gave it one.
|
|
15689
|
-
phaseTitle: buildPhaseTitleSchema.optional(),
|
|
15690
|
-
// When that phase's graph was last reported.
|
|
15691
|
-
reportedAt: utcIso8601,
|
|
15692
|
-
// Present when work on this phase was reported with no approval of its scope in force.
|
|
15693
|
-
builtWithoutApproval: external_exports.literal(true).optional()
|
|
15694
|
-
}).strict();
|
|
15695
|
-
var buildSnapshotReportSchema = external_exports.object({
|
|
15696
|
-
// Which phase this graph belongs to.
|
|
15697
|
-
phase: buildPhaseSchema,
|
|
15698
|
-
// That phase's name in words, when the project gives it one.
|
|
15699
|
-
phaseTitle: buildPhaseTitleSchema.optional(),
|
|
15700
|
-
// The whole graph, every time. Reporting a change instead of the graph is the
|
|
15701
|
-
// one thing this route does not accept.
|
|
15702
|
-
tasks: buildTaskListSchema
|
|
15703
|
-
}).strict();
|
|
15704
|
-
var buildSnapshotReportResultSchema = external_exports.object({
|
|
15705
|
-
// What happened. One outcome today; a refusal travels as the shared wire error.
|
|
15706
|
-
outcome: external_exports.literal("recorded"),
|
|
15707
|
-
// Which phase the graph was recorded against.
|
|
15708
|
-
phase: buildPhaseSchema,
|
|
15709
|
-
// When it was recorded.
|
|
15710
|
-
reportedAt: utcIso8601
|
|
15711
|
-
}).strict();
|
|
15732
|
+
}
|
|
15733
|
+
var buildRecordV2Schema = external_exports.object({
|
|
15734
|
+
format: external_exports.literal(BUILD_RECORD_V2_FORMAT),
|
|
15735
|
+
phase: phaseNameSchema,
|
|
15736
|
+
observed: observedSchema,
|
|
15737
|
+
attested: attestedSchema
|
|
15738
|
+
}).strict().superRefine(checkRecordHalves);
|
|
15712
15739
|
|
|
15713
15740
|
// ../events/dist/system-description.js
|
|
15714
15741
|
var SYSTEM_DESCRIPTION_FORMAT = 1;
|
|
@@ -15782,6 +15809,72 @@ var systemDescriptionProblemSchema = external_exports.object({
|
|
|
15782
15809
|
path: external_exports.string(),
|
|
15783
15810
|
message: external_exports.string()
|
|
15784
15811
|
}).strict();
|
|
15812
|
+
function segmentMatches(pattern, segment) {
|
|
15813
|
+
const literals = pattern.split("*");
|
|
15814
|
+
if (literals.length === 1)
|
|
15815
|
+
return pattern === segment;
|
|
15816
|
+
const first = literals[0];
|
|
15817
|
+
const last = literals[literals.length - 1];
|
|
15818
|
+
if (!segment.startsWith(first))
|
|
15819
|
+
return false;
|
|
15820
|
+
let position = first.length;
|
|
15821
|
+
for (const literal2 of literals.slice(1, -1)) {
|
|
15822
|
+
const found = segment.indexOf(literal2, position + 1);
|
|
15823
|
+
if (found === -1)
|
|
15824
|
+
return false;
|
|
15825
|
+
position = found + literal2.length;
|
|
15826
|
+
}
|
|
15827
|
+
return segment.length - last.length >= position + 1 && segment.endsWith(last);
|
|
15828
|
+
}
|
|
15829
|
+
function patternOwns(pattern, path) {
|
|
15830
|
+
const want = pattern.split("/");
|
|
15831
|
+
const have = path.split("/");
|
|
15832
|
+
const width = have.length + 1;
|
|
15833
|
+
const seen = new Uint8Array((want.length + 1) * width);
|
|
15834
|
+
const from = (i, j) => {
|
|
15835
|
+
if (i === want.length)
|
|
15836
|
+
return true;
|
|
15837
|
+
const cell = i * width + j;
|
|
15838
|
+
if (seen[cell] !== 0)
|
|
15839
|
+
return seen[cell] === 2;
|
|
15840
|
+
const segment = want[i];
|
|
15841
|
+
const result = segment === "**" ? from(i + 1, j) || j < have.length && from(i, j + 1) : j < have.length && segmentMatches(segment, have[j]) && from(i + 1, j + 1);
|
|
15842
|
+
seen[cell] = result ? 2 : 1;
|
|
15843
|
+
return result;
|
|
15844
|
+
};
|
|
15845
|
+
return from(0, 0);
|
|
15846
|
+
}
|
|
15847
|
+
function specificity(pattern) {
|
|
15848
|
+
const segments = pattern.split("/");
|
|
15849
|
+
const firstWildcard = segments.findIndex((segment) => segment.includes("*"));
|
|
15850
|
+
return {
|
|
15851
|
+
leadingLiterals: firstWildcard === -1 ? segments.length : firstWildcard,
|
|
15852
|
+
wildcards: segments.filter((segment) => segment.includes("*")).length,
|
|
15853
|
+
literalCharacters: pattern.replace(/[*/]/g, "").length
|
|
15854
|
+
};
|
|
15855
|
+
}
|
|
15856
|
+
function outranks(a, b) {
|
|
15857
|
+
if (a.rank.leadingLiterals !== b.rank.leadingLiterals)
|
|
15858
|
+
return b.rank.leadingLiterals - a.rank.leadingLiterals;
|
|
15859
|
+
if (a.rank.wildcards !== b.rank.wildcards)
|
|
15860
|
+
return a.rank.wildcards - b.rank.wildcards;
|
|
15861
|
+
if (a.rank.literalCharacters !== b.rank.literalCharacters)
|
|
15862
|
+
return b.rank.literalCharacters - a.rank.literalCharacters;
|
|
15863
|
+
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
15864
|
+
}
|
|
15865
|
+
function ownerOf(path, description) {
|
|
15866
|
+
let best = null;
|
|
15867
|
+
for (const part of description.parts) {
|
|
15868
|
+
for (const pattern of part.owns) {
|
|
15869
|
+
if (!patternOwns(pattern, path))
|
|
15870
|
+
continue;
|
|
15871
|
+
const candidate = { id: part.id, rank: specificity(pattern) };
|
|
15872
|
+
if (best === null || outranks(candidate, best) < 0)
|
|
15873
|
+
best = candidate;
|
|
15874
|
+
}
|
|
15875
|
+
}
|
|
15876
|
+
return best === null ? null : best.id;
|
|
15877
|
+
}
|
|
15785
15878
|
var systemReportRecordedSchema = external_exports.object({
|
|
15786
15879
|
outcome: external_exports.literal("recorded"),
|
|
15787
15880
|
version: external_exports.number().int().min(1),
|
|
@@ -15811,9 +15904,14 @@ var systemDrawPhaseRequestSchema = external_exports.object({
|
|
|
15811
15904
|
phase: phaseIdentitySchema,
|
|
15812
15905
|
planVersion: external_exports.number().int().min(1)
|
|
15813
15906
|
}).strict();
|
|
15907
|
+
var systemDrawClosedPhaseRequestSchema = external_exports.object({
|
|
15908
|
+
picture: external_exports.literal("closed-phase"),
|
|
15909
|
+
phase: phaseIdentitySchema
|
|
15910
|
+
}).strict();
|
|
15814
15911
|
var systemDrawRequestSchema = external_exports.discriminatedUnion("picture", [
|
|
15815
15912
|
systemDrawArchitectureRequestSchema,
|
|
15816
|
-
systemDrawPhaseRequestSchema
|
|
15913
|
+
systemDrawPhaseRequestSchema,
|
|
15914
|
+
systemDrawClosedPhaseRequestSchema
|
|
15817
15915
|
]);
|
|
15818
15916
|
function utf8ByteLength2(text) {
|
|
15819
15917
|
let bytes = 0;
|
|
@@ -15852,58 +15950,232 @@ var systemDrawRefusedSchema = external_exports.object({
|
|
|
15852
15950
|
var systemDrawUnavailableSchema = external_exports.object({
|
|
15853
15951
|
outcome: external_exports.literal("unavailable")
|
|
15854
15952
|
}).strict();
|
|
15855
|
-
|
|
15856
|
-
|
|
15857
|
-
var PHASE_PLAN_FORMAT = 1;
|
|
15858
|
-
var PHASE_PLAN_CHANGES_MAX = 60;
|
|
15859
|
-
var PHASE_PLAN_SUMMARY_MAX = 160;
|
|
15860
|
-
var SCOPE_NOTE_MAX = 500;
|
|
15861
|
-
var PHASE_PLAN_PHASE_MAX = buildPhaseSchema.maxLength ?? 0;
|
|
15862
|
-
var PHASE_PLAN_CHANGE_KINDS = ["add", "change", "remove"];
|
|
15863
|
-
var phasePlanChangeSchema = external_exports.enum(PHASE_PLAN_CHANGE_KINDS, {
|
|
15864
|
-
error: `must be one of ${PHASE_PLAN_CHANGE_KINDS.join(", ")}`
|
|
15865
|
-
});
|
|
15866
|
-
var phasePlanPhaseSchema = phaseIdentitySchema.refine((v) => !isValidPhaseIdentity(v) || buildPhaseSchema.safeParse(String(v)).success, { message: `must be at most ${PHASE_PLAN_PHASE_MAX} characters` });
|
|
15867
|
-
var phasePlanSummarySchema = descriptionText(PHASE_PLAN_SUMMARY_MAX);
|
|
15868
|
-
var phasePlanEntrySchema = external_exports.object({
|
|
15869
|
-
part: systemPartIdSchema,
|
|
15870
|
-
change: phasePlanChangeSchema,
|
|
15871
|
-
summary: phasePlanSummarySchema
|
|
15872
|
-
}).strict();
|
|
15873
|
-
var phasePlanSchema = external_exports.object({
|
|
15874
|
-
format: external_exports.literal(PHASE_PLAN_FORMAT, { error: `must be ${PHASE_PLAN_FORMAT}` }),
|
|
15875
|
-
phase: phasePlanPhaseSchema,
|
|
15876
|
-
changes: external_exports.array(phasePlanEntrySchema).min(1, { message: "must list at least one part" }).max(PHASE_PLAN_CHANGES_MAX, { message: `can list at most ${PHASE_PLAN_CHANGES_MAX} parts` })
|
|
15953
|
+
var systemDrawNotClosedSchema = external_exports.object({
|
|
15954
|
+
outcome: external_exports.literal("not-closed")
|
|
15877
15955
|
}).strict();
|
|
15878
|
-
|
|
15879
|
-
|
|
15880
|
-
|
|
15881
|
-
|
|
15882
|
-
"
|
|
15883
|
-
"
|
|
15884
|
-
"
|
|
15885
|
-
"
|
|
15886
|
-
|
|
15887
|
-
|
|
15888
|
-
|
|
15889
|
-
var
|
|
15890
|
-
var
|
|
15891
|
-
var
|
|
15892
|
-
var
|
|
15893
|
-
var
|
|
15894
|
-
var
|
|
15895
|
-
var
|
|
15896
|
-
|
|
15897
|
-
|
|
15898
|
-
|
|
15899
|
-
|
|
15900
|
-
|
|
15901
|
-
|
|
15902
|
-
|
|
15903
|
-
|
|
15956
|
+
|
|
15957
|
+
// ../events/dist/build-snapshot.js
|
|
15958
|
+
var buildTaskStateSchema = external_exports.enum([
|
|
15959
|
+
"pending",
|
|
15960
|
+
"in-flight",
|
|
15961
|
+
"completed",
|
|
15962
|
+
"failed",
|
|
15963
|
+
"blocked"
|
|
15964
|
+
]);
|
|
15965
|
+
var BUILD_TASK_TITLE_MAX = 240;
|
|
15966
|
+
var BUILD_SNAPSHOT_TASK_LIMIT = 500;
|
|
15967
|
+
var BUILD_TASK_WHY_OUTSIDE_PLAN_MAX = 160;
|
|
15968
|
+
var BUILD_TASK_BUDGET_MAX_MINUTES = 10080;
|
|
15969
|
+
var BUILD_TASK_NOTE_MAX = 240;
|
|
15970
|
+
var BUILD_TASK_CLEAR_COMMAND_MAX = 240;
|
|
15971
|
+
var buildTaskKindSchema = external_exports.enum(["build", "review", "spec", "walk"]);
|
|
15972
|
+
var buildTaskIdSchema = external_exports.string().min(1).max(64);
|
|
15973
|
+
var buildTaskSchema = external_exports.object({
|
|
15974
|
+
// The task's own id, as the coordinator's graph names it.
|
|
15975
|
+
id: buildTaskIdSchema,
|
|
15976
|
+
// What the task is, in the coordinator's words. A description of the work,
|
|
15977
|
+
// bounded and scanned — never an excerpt of anybody's code.
|
|
15978
|
+
title: external_exports.string().min(1).max(BUILD_TASK_TITLE_MAX),
|
|
15979
|
+
// What the task is doing right now.
|
|
15980
|
+
state: buildTaskStateSchema,
|
|
15981
|
+
// Who holds it — a bare callsign, or the value naming a person when the work is
|
|
15982
|
+
// a person's. Absent when nothing has been dispatched to it yet.
|
|
15983
|
+
agent: agentNameSchema.optional(),
|
|
15984
|
+
// The kind of work the holder does. Present exactly when an agent holds it.
|
|
15985
|
+
discipline: external_exports.string().min(1).max(64).optional(),
|
|
15986
|
+
// How much thinking the holder brings. Present exactly when an agent holds it,
|
|
15987
|
+
// and there is no field naming the supplier behind it.
|
|
15988
|
+
tier: external_exports.string().min(1).max(32).optional(),
|
|
15989
|
+
// The tasks this one cannot start before. Always present; empty means it waits
|
|
15990
|
+
// on nothing, which is different from not having been computed.
|
|
15991
|
+
dependsOn: external_exports.array(buildTaskIdSchema).max(BUILD_SNAPSHOT_TASK_LIMIT),
|
|
15992
|
+
// The one task this task checks, when it is a check on other work. Absent on a
|
|
15993
|
+
// task that is doing the work rather than checking it.
|
|
15994
|
+
reviews: buildTaskIdSchema.optional(),
|
|
15995
|
+
// Which wave of the phase this task belongs to — the wave that writes the
|
|
15996
|
+
// specifications, or the wave that builds them. Absent when the graph does not
|
|
15997
|
+
// record it, which is different from the task belonging to neither.
|
|
15998
|
+
layer: methodLayerSchema.optional(),
|
|
15999
|
+
// When it was picked up, and when it settled. A reader computes how long it
|
|
16000
|
+
// took; neither this row nor this payload carries a duration.
|
|
16001
|
+
startedAt: utcIso8601.optional(),
|
|
16002
|
+
settledAt: utcIso8601.optional(),
|
|
16003
|
+
// The parts of this project's system the task builds, by their ids. Absent when
|
|
16004
|
+
// it builds none; never an empty list.
|
|
16005
|
+
builds: external_exports.array(systemPartIdSchema).min(1).max(SYSTEM_PARTS_MAX).optional(),
|
|
16006
|
+
// In one line, why the task built a part the phase's plan does not include, in
|
|
16007
|
+
// the words of whoever holds it. Absent when it gave no reason.
|
|
16008
|
+
whyOutsidePlan: descriptionText(BUILD_TASK_WHY_OUTSIDE_PLAN_MAX).optional(),
|
|
16009
|
+
// How long the task is expected to take, in whole minutes, from one minute to one
|
|
16010
|
+
// week. A plan, not a measurement; absent when no budget was set.
|
|
16011
|
+
budgetMinutes: external_exports.number().int().min(1).max(BUILD_TASK_BUDGET_MAX_MINUTES).optional(),
|
|
16012
|
+
// What kind of work the task is: building, checking other work, writing what to
|
|
16013
|
+
// build, or a person trying the product. Absent when the graph does not say.
|
|
16014
|
+
kind: buildTaskKindSchema.optional(),
|
|
16015
|
+
// One line from whoever holds the task about where it stands. Absent when there
|
|
16016
|
+
// is nothing to say.
|
|
16017
|
+
note: descriptionText(BUILD_TASK_NOTE_MAX).optional(),
|
|
16018
|
+
// On a task held for a person, the one thing they do or run to clear it, in one
|
|
16019
|
+
// line. Absent when the task names none.
|
|
16020
|
+
clearCommand: descriptionText(BUILD_TASK_CLEAR_COMMAND_MAX).optional(),
|
|
16021
|
+
// On a task held for a person, the crew member ready to take it on once it is
|
|
16022
|
+
// cleared, by callsign. Absent when nobody is standing by.
|
|
16023
|
+
standingBy: agentNameSchema.optional()
|
|
16024
|
+
}).strict().superRefine((task, ctx) => {
|
|
16025
|
+
if (task.reviews !== void 0 && task.reviews === task.id) {
|
|
16026
|
+
ctx.addIssue({
|
|
16027
|
+
code: external_exports.ZodIssueCode.custom,
|
|
16028
|
+
path: ["reviews"],
|
|
16029
|
+
message: `'${task.id}' reviews itself \u2014 a review names the OTHER task it checks.`
|
|
16030
|
+
});
|
|
16031
|
+
}
|
|
16032
|
+
if (task.layer !== void 0 && !isPhaseScopedLayer(task.layer)) {
|
|
16033
|
+
ctx.addIssue({
|
|
16034
|
+
code: external_exports.ZodIssueCode.custom,
|
|
16035
|
+
path: ["layer"],
|
|
16036
|
+
message: `'${task.layer}' is not a wave a phase runs \u2014 expected one of ${PHASE_SCOPED_LAYERS.join(", ")}.`
|
|
16037
|
+
});
|
|
16038
|
+
}
|
|
16039
|
+
const profiled = task.agent !== void 0 && task.agent !== HUMAN_AGENT;
|
|
16040
|
+
for (const field of ["discipline", "tier"]) {
|
|
16041
|
+
if (profiled && task[field] === void 0) {
|
|
16042
|
+
ctx.addIssue({
|
|
16043
|
+
code: external_exports.ZodIssueCode.custom,
|
|
16044
|
+
path: [field],
|
|
16045
|
+
message: `'${field}' is required on a task an agent holds.`
|
|
16046
|
+
});
|
|
16047
|
+
}
|
|
16048
|
+
if (!profiled && task[field] !== void 0) {
|
|
16049
|
+
ctx.addIssue({
|
|
16050
|
+
code: external_exports.ZodIssueCode.custom,
|
|
16051
|
+
path: [field],
|
|
16052
|
+
message: task.agent === void 0 ? `'${field}' must be absent on a task nobody holds.` : `'${field}' must be absent on work a person holds.`
|
|
16053
|
+
});
|
|
16054
|
+
}
|
|
16055
|
+
}
|
|
16056
|
+
});
|
|
16057
|
+
var buildPhaseSchema = external_exports.string().min(1).max(128);
|
|
16058
|
+
var BUILD_PHASE_TITLE_MAX = 160;
|
|
16059
|
+
var buildPhaseTitleSchema = external_exports.string().min(1).max(BUILD_PHASE_TITLE_MAX);
|
|
16060
|
+
var buildTaskListSchema = external_exports.array(buildTaskSchema).max(BUILD_SNAPSHOT_TASK_LIMIT).superRefine((tasks, ctx) => {
|
|
16061
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16062
|
+
for (const [index, task] of tasks.entries()) {
|
|
16063
|
+
if (seen.has(task.id)) {
|
|
16064
|
+
ctx.addIssue({
|
|
16065
|
+
code: external_exports.ZodIssueCode.custom,
|
|
16066
|
+
path: [index, "id"],
|
|
16067
|
+
message: `duplicate task id '${task.id}' \u2014 one graph names each task once.`
|
|
16068
|
+
});
|
|
16069
|
+
}
|
|
16070
|
+
seen.add(task.id);
|
|
16071
|
+
}
|
|
16072
|
+
});
|
|
16073
|
+
var buildTaskMarkSchema = external_exports.object({
|
|
16074
|
+
// The task that was marked.
|
|
16075
|
+
task: buildTaskIdSchema,
|
|
16076
|
+
// When it was first marked.
|
|
16077
|
+
at: utcIso8601
|
|
16078
|
+
}).strict();
|
|
16079
|
+
var buildSnapshotSchema = external_exports.object({
|
|
16080
|
+
// Which phase this graph belongs to.
|
|
16081
|
+
phase: buildPhaseSchema,
|
|
16082
|
+
// That phase's name in words, when the project gives it one.
|
|
16083
|
+
phaseTitle: buildPhaseTitleSchema.optional(),
|
|
16084
|
+
// Every task on it. Never a partial graph and never a change since last time.
|
|
16085
|
+
tasks: buildTaskListSchema,
|
|
16086
|
+
// When this graph was last reported.
|
|
16087
|
+
reportedAt: utcIso8601,
|
|
16088
|
+
// When work on this phase was first reported with no approval of its scope in force.
|
|
16089
|
+
builtWithoutApproval: utcIso8601.optional(),
|
|
16090
|
+
// Each task that started on a part still waiting for approval, and when it was
|
|
16091
|
+
// first seen doing so. Absent when there is none.
|
|
16092
|
+
tasksBuiltWithoutApproval: external_exports.array(buildTaskMarkSchema).min(1).max(BUILD_SNAPSHOT_TASK_LIMIT).optional()
|
|
16093
|
+
}).strict();
|
|
16094
|
+
var buildPhaseEntrySchema = external_exports.object({
|
|
16095
|
+
// Which phase has a stored graph.
|
|
16096
|
+
phase: buildPhaseSchema,
|
|
16097
|
+
// That phase's name in words, when the project gave it one.
|
|
16098
|
+
phaseTitle: buildPhaseTitleSchema.optional(),
|
|
16099
|
+
// When that phase's graph was last reported.
|
|
16100
|
+
reportedAt: utcIso8601,
|
|
16101
|
+
// Present when work on this phase was reported with no approval of its scope in force.
|
|
16102
|
+
builtWithoutApproval: external_exports.literal(true).optional()
|
|
16103
|
+
}).strict();
|
|
16104
|
+
var buildSnapshotReportSchema = external_exports.object({
|
|
16105
|
+
// Which phase this graph belongs to.
|
|
16106
|
+
phase: buildPhaseSchema,
|
|
16107
|
+
// That phase's name in words, when the project gives it one.
|
|
16108
|
+
phaseTitle: buildPhaseTitleSchema.optional(),
|
|
16109
|
+
// The whole graph, every time. Reporting a change instead of the graph is the
|
|
16110
|
+
// one thing this route does not accept.
|
|
16111
|
+
tasks: buildTaskListSchema
|
|
16112
|
+
}).strict();
|
|
16113
|
+
var buildSnapshotReportResultSchema = external_exports.object({
|
|
16114
|
+
// What happened. One outcome today; a refusal travels as the shared wire error.
|
|
16115
|
+
outcome: external_exports.literal("recorded"),
|
|
16116
|
+
// Which phase the graph was recorded against.
|
|
16117
|
+
phase: buildPhaseSchema,
|
|
16118
|
+
// When it was recorded.
|
|
16119
|
+
reportedAt: utcIso8601
|
|
16120
|
+
}).strict();
|
|
16121
|
+
|
|
16122
|
+
// ../events/dist/phase-plan.js
|
|
16123
|
+
var PHASE_PLAN_FORMAT = 1;
|
|
16124
|
+
var PHASE_PLAN_CHANGES_MAX = 60;
|
|
16125
|
+
var PHASE_PLAN_SUMMARY_MAX = 160;
|
|
16126
|
+
var SCOPE_NOTE_MAX = 500;
|
|
16127
|
+
var PHASE_PLAN_PHASE_MAX = buildPhaseSchema.maxLength ?? 0;
|
|
16128
|
+
var PHASE_PLAN_CHANGE_KINDS = ["add", "change", "remove"];
|
|
16129
|
+
var phasePlanChangeSchema = external_exports.enum(PHASE_PLAN_CHANGE_KINDS, {
|
|
16130
|
+
error: `must be one of ${PHASE_PLAN_CHANGE_KINDS.join(", ")}`
|
|
16131
|
+
});
|
|
16132
|
+
var phasePlanPhaseSchema = phaseIdentitySchema.refine((v) => !isValidPhaseIdentity(v) || buildPhaseSchema.safeParse(String(v)).success, { message: `must be at most ${PHASE_PLAN_PHASE_MAX} characters` });
|
|
16133
|
+
var phasePlanSummarySchema = descriptionText(PHASE_PLAN_SUMMARY_MAX);
|
|
16134
|
+
var phasePlanEntrySchema = external_exports.object({
|
|
16135
|
+
part: systemPartIdSchema,
|
|
16136
|
+
change: phasePlanChangeSchema,
|
|
16137
|
+
summary: phasePlanSummarySchema
|
|
16138
|
+
}).strict();
|
|
16139
|
+
var phasePlanSchema = external_exports.object({
|
|
16140
|
+
format: external_exports.literal(PHASE_PLAN_FORMAT, { error: `must be ${PHASE_PLAN_FORMAT}` }),
|
|
16141
|
+
phase: phasePlanPhaseSchema,
|
|
16142
|
+
changes: external_exports.array(phasePlanEntrySchema).min(1, { message: "must list at least one part" }).max(PHASE_PLAN_CHANGES_MAX, { message: `can list at most ${PHASE_PLAN_CHANGES_MAX} parts` })
|
|
16143
|
+
}).strict();
|
|
16144
|
+
var scopePhaseSchema = buildPhaseSchema.refine((v) => isValidPhaseIdentity(v), {
|
|
16145
|
+
message: "must be a phase name"
|
|
16146
|
+
});
|
|
16147
|
+
var SCOPE_STATE_NAMES = [
|
|
16148
|
+
"not-planned",
|
|
16149
|
+
"waiting",
|
|
16150
|
+
"sent-back",
|
|
16151
|
+
"approved",
|
|
16152
|
+
"partly-stale"
|
|
16153
|
+
];
|
|
16154
|
+
var scopeStateNameSchema = external_exports.enum(SCOPE_STATE_NAMES);
|
|
16155
|
+
var plannedScopeStateSchema = scopeStateNameSchema.exclude(["not-planned"]);
|
|
16156
|
+
var SCOPE_PART_STATUSES = ["approved", "waiting"];
|
|
16157
|
+
var scopePartStatusSchema = external_exports.enum(SCOPE_PART_STATUSES);
|
|
16158
|
+
var SCOPE_ACTS = ["approve", "send-back"];
|
|
16159
|
+
var scopeActSchema = external_exports.enum(SCOPE_ACTS);
|
|
16160
|
+
var SCOPE_GIVEN_IN = ["console", "chat"];
|
|
16161
|
+
var scopeGivenInSchema = external_exports.enum(SCOPE_GIVEN_IN);
|
|
16162
|
+
var scopeNoteSchema = descriptionText(SCOPE_NOTE_MAX);
|
|
16163
|
+
var scopeNoteWireSchema = external_exports.string().max(SCOPE_NOTE_MAX);
|
|
16164
|
+
var SCOPE_PART_VERDICTS = ["ok", "change"];
|
|
16165
|
+
var scopePartVerdictSchema = external_exports.enum(SCOPE_PART_VERDICTS);
|
|
16166
|
+
var scopeDecisionPartSchema = external_exports.object({
|
|
16167
|
+
part: systemPartIdSchema,
|
|
16168
|
+
verdict: scopePartVerdictSchema,
|
|
15904
16169
|
note: scopeNoteWireSchema.optional()
|
|
15905
16170
|
}).strict();
|
|
15906
16171
|
var planVersionSchema = external_exports.number().int().min(1);
|
|
16172
|
+
var carriedPhaseSchema = external_exports.object({
|
|
16173
|
+
phase: scopePhaseSchema,
|
|
16174
|
+
number: external_exports.number().int().min(1)
|
|
16175
|
+
}).strict();
|
|
16176
|
+
var carriedFromEntrySchema = carriedPhaseSchema.extend({
|
|
16177
|
+
part: systemPartIdSchema
|
|
16178
|
+
}).strict();
|
|
15907
16179
|
var phasePlanRequestSchema = external_exports.object({
|
|
15908
16180
|
descriptionVersion: external_exports.number().int().min(1),
|
|
15909
16181
|
plan: external_exports.unknown()
|
|
@@ -15915,7 +16187,8 @@ var phasePlanRecordedSchema = external_exports.object({
|
|
|
15915
16187
|
state: plannedScopeStateSchema,
|
|
15916
16188
|
changedParts: external_exports.array(systemPartIdSchema).max(PHASE_PLAN_CHANGES_MAX),
|
|
15917
16189
|
typedApprovalOffered: external_exports.boolean(),
|
|
15918
|
-
reportedAt: utcIso8601
|
|
16190
|
+
reportedAt: utcIso8601,
|
|
16191
|
+
carriedFrom: external_exports.array(carriedFromEntrySchema).min(1).max(PHASE_PLAN_CHANGES_MAX).optional()
|
|
15919
16192
|
}).strict();
|
|
15920
16193
|
var phasePlanRefusedSchema = external_exports.object({
|
|
15921
16194
|
outcome: external_exports.literal("refused"),
|
|
@@ -15924,10 +16197,23 @@ var phasePlanRefusedSchema = external_exports.object({
|
|
|
15924
16197
|
var phasePlanUnavailableSchema = external_exports.object({
|
|
15925
16198
|
outcome: external_exports.literal("unavailable")
|
|
15926
16199
|
}).strict();
|
|
16200
|
+
var REMOVAL_OWED_ACTS = ["dropped", "removed"];
|
|
16201
|
+
var removalOwedPartSchema = external_exports.object({
|
|
16202
|
+
part: systemPartIdSchema,
|
|
16203
|
+
name: systemPartSchema.shape.name,
|
|
16204
|
+
phase: scopePhaseSchema,
|
|
16205
|
+
number: external_exports.number().int().min(1),
|
|
16206
|
+
act: external_exports.enum(REMOVAL_OWED_ACTS)
|
|
16207
|
+
}).strict();
|
|
16208
|
+
var phasePlanRemovalOwedSchema = external_exports.object({
|
|
16209
|
+
outcome: external_exports.literal("removal-owed"),
|
|
16210
|
+
parts: external_exports.array(removalOwedPartSchema).min(1).max(SYSTEM_PARTS_MAX)
|
|
16211
|
+
}).strict();
|
|
15927
16212
|
var phasePlanResultSchema = external_exports.discriminatedUnion("outcome", [
|
|
15928
16213
|
phasePlanRecordedSchema,
|
|
15929
16214
|
phasePlanRefusedSchema,
|
|
15930
|
-
phasePlanUnavailableSchema
|
|
16215
|
+
phasePlanUnavailableSchema,
|
|
16216
|
+
phasePlanRemovalOwedSchema
|
|
15931
16217
|
]);
|
|
15932
16218
|
var scopeApproveRequestSchema = external_exports.object({
|
|
15933
16219
|
act: external_exports.literal("approve"),
|
|
@@ -15940,14 +16226,9 @@ var scopeSendBackRequestSchema = external_exports.object({
|
|
|
15940
16226
|
parts: external_exports.array(scopeDecisionPartSchema).max(PHASE_PLAN_CHANGES_MAX),
|
|
15941
16227
|
note: scopeNoteWireSchema.optional()
|
|
15942
16228
|
}).strict();
|
|
15943
|
-
var scopeWithdrawRequestSchema = external_exports.object({
|
|
15944
|
-
act: external_exports.literal("withdraw"),
|
|
15945
|
-
planVersion: planVersionSchema
|
|
15946
|
-
}).strict();
|
|
15947
16229
|
var scopeDecisionRequestSchema = external_exports.discriminatedUnion("act", [
|
|
15948
16230
|
scopeApproveRequestSchema,
|
|
15949
|
-
scopeSendBackRequestSchema
|
|
15950
|
-
scopeWithdrawRequestSchema
|
|
16231
|
+
scopeSendBackRequestSchema
|
|
15951
16232
|
]);
|
|
15952
16233
|
var scopeDecisionRecordedSchema = external_exports.object({
|
|
15953
16234
|
outcome: external_exports.literal("recorded"),
|
|
@@ -15980,7 +16261,6 @@ var scopeDecisionResultSchema = external_exports.discriminatedUnion("outcome", [
|
|
|
15980
16261
|
reasonOutcome("not-owner"),
|
|
15981
16262
|
reasonOutcome("no-owner"),
|
|
15982
16263
|
reasonOutcome("plan-changed"),
|
|
15983
|
-
reasonOutcome("nothing-to-withdraw"),
|
|
15984
16264
|
reasonOutcome("build-started")
|
|
15985
16265
|
]);
|
|
15986
16266
|
var scopeReadPartSchema = external_exports.object({
|
|
@@ -15989,7 +16269,8 @@ var scopeReadPartSchema = external_exports.object({
|
|
|
15989
16269
|
change: phasePlanChangeSchema,
|
|
15990
16270
|
summary: phasePlanSummarySchema,
|
|
15991
16271
|
status: scopePartStatusSchema,
|
|
15992
|
-
changed: external_exports.boolean()
|
|
16272
|
+
changed: external_exports.boolean(),
|
|
16273
|
+
carriedFrom: carriedPhaseSchema.optional()
|
|
15993
16274
|
}).strict();
|
|
15994
16275
|
var scopeReadDecisionSchema = external_exports.object({
|
|
15995
16276
|
act: scopeActSchema,
|
|
@@ -16016,75 +16297,581 @@ var scopeReadPlannedSchema = external_exports.object({
|
|
|
16016
16297
|
}).strict();
|
|
16017
16298
|
var scopeReadSchema = external_exports.union([scopeReadNotPlannedSchema, scopeReadPlannedSchema]);
|
|
16018
16299
|
|
|
16019
|
-
// ../events/dist/
|
|
16020
|
-
var
|
|
16021
|
-
var
|
|
16022
|
-
|
|
16300
|
+
// ../events/dist/code-link.js
|
|
16301
|
+
var codeLinkAddressSchema = external_exports.string().regex(GITHUB_REPOSITORY_ADDRESS, { message: "must be a GitHub repository address: https://github.com/<owner>/<repo>" }).refine((v) => !looksLikeSecret(v), { message: "looks like a password or key, so it is not sent" });
|
|
16302
|
+
var codeLinkFactsSchema = external_exports.object({
|
|
16303
|
+
repositoryAddress: codeLinkAddressSchema.nullable(),
|
|
16304
|
+
headOnGitHub: external_exports.boolean()
|
|
16305
|
+
}).strict().refine((v) => !(v.headOnGitHub && v.repositoryAddress === null), {
|
|
16306
|
+
message: "cannot be true when the code is not on GitHub",
|
|
16307
|
+
path: ["headOnGitHub"]
|
|
16308
|
+
});
|
|
16309
|
+
var PART_CODE_LINK_SCOPES = ["part", "phase"];
|
|
16310
|
+
var PART_CODE_LINK_RANGE_REASONS = ["not-github", "not-pushed"];
|
|
16311
|
+
var PART_CODE_LINK_URL_PREFIX = "https://github.com/";
|
|
16312
|
+
var partCodeLinkGithubSchema = external_exports.object({
|
|
16313
|
+
kind: external_exports.literal("github"),
|
|
16314
|
+
scope: external_exports.enum(PART_CODE_LINK_SCOPES),
|
|
16315
|
+
url: external_exports.string().startsWith(PART_CODE_LINK_URL_PREFIX, { message: "must start with https://github.com/" })
|
|
16316
|
+
}).strict();
|
|
16317
|
+
var partCodeLinkRangeSchema = external_exports.object({
|
|
16318
|
+
kind: external_exports.literal("range"),
|
|
16319
|
+
why: external_exports.enum(PART_CODE_LINK_RANGE_REASONS),
|
|
16320
|
+
from: closeCommitSchema,
|
|
16321
|
+
to: closeCommitSchema
|
|
16322
|
+
}).strict();
|
|
16323
|
+
var partCodeLinkNoneSchema = external_exports.object({
|
|
16324
|
+
kind: external_exports.literal("none")
|
|
16325
|
+
}).strict();
|
|
16326
|
+
var partCodeLinkSchema = external_exports.discriminatedUnion("kind", [
|
|
16327
|
+
partCodeLinkGithubSchema,
|
|
16328
|
+
partCodeLinkRangeSchema,
|
|
16329
|
+
partCodeLinkNoneSchema
|
|
16330
|
+
]);
|
|
16023
16331
|
|
|
16024
|
-
// ../events/dist/
|
|
16025
|
-
var
|
|
16026
|
-
var
|
|
16027
|
-
|
|
16028
|
-
|
|
16332
|
+
// ../events/dist/observed-changes.js
|
|
16333
|
+
var OBSERVED_PATHS_LISTED_MAX = 50;
|
|
16334
|
+
var OBSERVED_PATH_MAX = 300;
|
|
16335
|
+
var OBSERVED_COUNT_MAX = 1e6;
|
|
16336
|
+
var METHOD_FILE_PATTERNS = [
|
|
16337
|
+
"CLAUDE.md",
|
|
16338
|
+
"AGENTS.md",
|
|
16339
|
+
".claude/context",
|
|
16340
|
+
".claude/commands",
|
|
16341
|
+
".claude/hooks",
|
|
16342
|
+
".claude/settings.json",
|
|
16343
|
+
".mcp.json",
|
|
16344
|
+
".halfcycle",
|
|
16345
|
+
".workbench",
|
|
16346
|
+
"docs/product-spec.md",
|
|
16347
|
+
"docs/architecture-overview.md",
|
|
16348
|
+
"docs/invariants.md",
|
|
16349
|
+
"docs/project-log.md",
|
|
16350
|
+
"docs/code-map.md",
|
|
16351
|
+
"docs/system.json",
|
|
16352
|
+
"docs/diagrams",
|
|
16353
|
+
"docs/features",
|
|
16354
|
+
"docs/phases",
|
|
16355
|
+
"docs/build-records",
|
|
16356
|
+
"docs/decisions",
|
|
16357
|
+
"docs/components"
|
|
16358
|
+
];
|
|
16359
|
+
var TEST_FILE_PATTERNS = [
|
|
16360
|
+
"**/*.test.*",
|
|
16361
|
+
"**/*.spec.*",
|
|
16362
|
+
"**/*_test.*",
|
|
16363
|
+
"**/*_spec.*",
|
|
16364
|
+
"**/test_*.*",
|
|
16365
|
+
"**/*Test.java",
|
|
16366
|
+
"**/*Tests.java",
|
|
16367
|
+
"**/*Test.kt",
|
|
16368
|
+
"**/*Tests.cs",
|
|
16369
|
+
"**/*Test.php"
|
|
16370
|
+
];
|
|
16371
|
+
function isMethodFile(path) {
|
|
16372
|
+
return METHOD_FILE_PATTERNS.some((pattern) => patternOwns(pattern, path));
|
|
16373
|
+
}
|
|
16374
|
+
function isTestFile(path) {
|
|
16375
|
+
return TEST_FILE_PATTERNS.some((pattern) => patternOwns(pattern, path));
|
|
16376
|
+
}
|
|
16377
|
+
var observedCountSchema = external_exports.number({ error: `must be a whole number from 0 to ${OBSERVED_COUNT_MAX}` }).int({ error: `must be a whole number from 0 to ${OBSERVED_COUNT_MAX}` }).min(0, { error: `must be a whole number from 0 to ${OBSERVED_COUNT_MAX}` }).max(OBSERVED_COUNT_MAX, { error: `must be a whole number from 0 to ${OBSERVED_COUNT_MAX}` });
|
|
16378
|
+
var observedDescriptionVersionSchema = external_exports.number().int().min(1);
|
|
16379
|
+
var observedPathSchema = external_exports.string().min(1, { message: "must not be empty" }).max(OBSERVED_PATH_MAX, { message: `must be at most ${OBSERVED_PATH_MAX} characters` });
|
|
16380
|
+
var observedPathListSchema = external_exports.array(observedPathSchema).max(OBSERVED_PATHS_LISTED_MAX, { message: `can list at most ${OBSERVED_PATHS_LISTED_MAX} paths` }).refine((paths) => new Set(paths).size === paths.length, { message: "must name each path once" });
|
|
16381
|
+
var observedMethodFilesSchema = external_exports.object({
|
|
16382
|
+
added: observedCountSchema,
|
|
16383
|
+
changed: observedCountSchema,
|
|
16384
|
+
removed: observedCountSchema
|
|
16029
16385
|
}).strict();
|
|
16030
|
-
var
|
|
16031
|
-
|
|
16032
|
-
|
|
16033
|
-
|
|
16034
|
-
|
|
16035
|
-
|
|
16036
|
-
|
|
16037
|
-
|
|
16038
|
-
|
|
16039
|
-
|
|
16040
|
-
|
|
16041
|
-
|
|
16386
|
+
var observedFilesSchema = external_exports.object({
|
|
16387
|
+
added: observedCountSchema,
|
|
16388
|
+
changed: observedCountSchema,
|
|
16389
|
+
removed: observedCountSchema,
|
|
16390
|
+
testFilesAdded: observedCountSchema
|
|
16391
|
+
}).strict().refine((v) => v.testFilesAdded <= v.added, {
|
|
16392
|
+
message: "cannot be more than the files added",
|
|
16393
|
+
path: ["testFilesAdded"]
|
|
16394
|
+
});
|
|
16395
|
+
function filesChanged(files) {
|
|
16396
|
+
return files.added + files.changed + files.removed;
|
|
16397
|
+
}
|
|
16398
|
+
function pathsFitTheCount(files, paths) {
|
|
16399
|
+
return files === null ? paths.length === 0 : paths.length <= filesChanged(files);
|
|
16400
|
+
}
|
|
16401
|
+
function checkPartsUnique(parts, ctx, at) {
|
|
16402
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16403
|
+
parts.forEach((entry, i) => {
|
|
16404
|
+
if (seen.has(entry.part)) {
|
|
16405
|
+
ctx.addIssue({ code: "custom", path: [...at, i, "part"], message: `names the part "${entry.part}" twice` });
|
|
16042
16406
|
}
|
|
16407
|
+
seen.add(entry.part);
|
|
16408
|
+
});
|
|
16409
|
+
}
|
|
16410
|
+
var observedBasisCountSchema = external_exports.object({
|
|
16411
|
+
outcome: external_exports.literal("count"),
|
|
16412
|
+
phase: phaseNameSchema,
|
|
16413
|
+
startCommit: closeCommitSchema,
|
|
16414
|
+
descriptionVersion: observedDescriptionVersionSchema,
|
|
16415
|
+
description: systemDescriptionSchema
|
|
16416
|
+
}).strict();
|
|
16417
|
+
var observedBasisCannotCountSchema = external_exports.object({
|
|
16418
|
+
outcome: external_exports.literal("cannot-count"),
|
|
16419
|
+
phase: phaseNameSchema,
|
|
16420
|
+
reason: external_exports.literal("no-start-commit")
|
|
16421
|
+
}).strict();
|
|
16422
|
+
var observedBasisNothingToCountSchema = external_exports.object({
|
|
16423
|
+
outcome: external_exports.literal("nothing-to-count"),
|
|
16424
|
+
reason: external_exports.enum(["no-open-phase", "no-description"])
|
|
16425
|
+
}).strict();
|
|
16426
|
+
var observedBasisSchema = external_exports.discriminatedUnion("outcome", [
|
|
16427
|
+
observedBasisCountSchema,
|
|
16428
|
+
observedBasisCannotCountSchema,
|
|
16429
|
+
observedBasisNothingToCountSchema
|
|
16430
|
+
]);
|
|
16431
|
+
var observedReportPartSchema = external_exports.object({
|
|
16432
|
+
part: systemPartIdSchema,
|
|
16433
|
+
added: observedCountSchema,
|
|
16434
|
+
changed: observedCountSchema,
|
|
16435
|
+
removed: observedCountSchema,
|
|
16436
|
+
testFilesAdded: observedCountSchema
|
|
16437
|
+
}).strict().refine((v) => v.testFilesAdded <= v.added, {
|
|
16438
|
+
message: "cannot be more than the files added",
|
|
16439
|
+
path: ["testFilesAdded"]
|
|
16440
|
+
}).refine((v) => filesChanged(v) >= 1, {
|
|
16441
|
+
message: "a part is listed only when at least one file changed under it",
|
|
16442
|
+
path: ["added"]
|
|
16443
|
+
});
|
|
16444
|
+
var observedReportOutsideSchema = external_exports.object({
|
|
16445
|
+
added: observedCountSchema,
|
|
16446
|
+
changed: observedCountSchema,
|
|
16447
|
+
removed: observedCountSchema,
|
|
16448
|
+
testFilesAdded: observedCountSchema,
|
|
16449
|
+
paths: observedPathListSchema
|
|
16450
|
+
}).strict().refine((v) => v.testFilesAdded <= v.added, {
|
|
16451
|
+
message: "cannot be more than the files added",
|
|
16452
|
+
path: ["testFilesAdded"]
|
|
16453
|
+
}).refine((v) => pathsFitTheCount(v, v.paths), {
|
|
16454
|
+
message: "lists more paths than files changed",
|
|
16455
|
+
path: ["paths"]
|
|
16456
|
+
});
|
|
16457
|
+
var observedChangesReportSchema = external_exports.object({
|
|
16458
|
+
phase: phaseNameSchema,
|
|
16459
|
+
startCommit: closeCommitSchema,
|
|
16460
|
+
headCommit: closeCommitSchema,
|
|
16461
|
+
headCommittedAt: utcIso8601,
|
|
16462
|
+
commits: observedCountSchema,
|
|
16463
|
+
descriptionVersion: observedDescriptionVersionSchema,
|
|
16464
|
+
parts: external_exports.array(observedReportPartSchema).max(SYSTEM_PARTS_MAX, { message: `can list at most ${SYSTEM_PARTS_MAX} parts` }),
|
|
16465
|
+
methodFiles: observedMethodFilesSchema,
|
|
16466
|
+
outsideEveryPart: observedReportOutsideSchema,
|
|
16467
|
+
codeLink: codeLinkFactsSchema
|
|
16468
|
+
}).strict().superRefine((v, ctx) => checkPartsUnique(v.parts, ctx, ["parts"]));
|
|
16469
|
+
var observedSaidSchema = external_exports.object({
|
|
16470
|
+
title: buildTaskSchema.shape.title,
|
|
16471
|
+
agent: buildTaskSchema.shape.agent,
|
|
16472
|
+
whyOutsidePlan: buildTaskSchema.shape.whyOutsidePlan
|
|
16473
|
+
}).strict();
|
|
16474
|
+
var observedNotInPlanSchema = external_exports.object({
|
|
16475
|
+
part: systemPartIdSchema,
|
|
16476
|
+
name: systemPartSchema.shape.name,
|
|
16477
|
+
said: external_exports.array(observedSaidSchema).max(BUILD_SNAPSHOT_TASK_LIMIT)
|
|
16478
|
+
}).strict();
|
|
16479
|
+
var observedChangesRecordedSchema = external_exports.object({
|
|
16480
|
+
outcome: external_exports.literal("recorded"),
|
|
16481
|
+
notInPlan: external_exports.array(observedNotInPlanSchema).max(SYSTEM_PARTS_MAX).superRefine((parts, ctx) => checkPartsUnique(parts, ctx, [])),
|
|
16482
|
+
outside: external_exports.object({
|
|
16483
|
+
files: observedCountSchema,
|
|
16484
|
+
example: observedPathSchema.nullable()
|
|
16485
|
+
}).strict().refine((v) => v.example === null || v.files >= 1, {
|
|
16486
|
+
message: "names an example when no file changed",
|
|
16487
|
+
path: ["example"]
|
|
16488
|
+
})
|
|
16489
|
+
}).strict();
|
|
16490
|
+
var observedChangesKeptSchema = external_exports.object({
|
|
16491
|
+
outcome: external_exports.literal("kept")
|
|
16492
|
+
}).strict();
|
|
16493
|
+
var observedChangesReportResultSchema = external_exports.discriminatedUnion("outcome", [
|
|
16494
|
+
observedChangesRecordedSchema,
|
|
16495
|
+
observedChangesKeptSchema
|
|
16496
|
+
]);
|
|
16497
|
+
var OBSERVE_NOT_SENT_REASONS = [
|
|
16498
|
+
"not-configured",
|
|
16499
|
+
"nothing-to-count",
|
|
16500
|
+
"cannot-count",
|
|
16501
|
+
"start-not-here",
|
|
16502
|
+
"start-not-on-branch",
|
|
16503
|
+
"basis-moved",
|
|
16504
|
+
"refused",
|
|
16505
|
+
"unreachable"
|
|
16506
|
+
];
|
|
16507
|
+
var observeRunRecordedSchema = external_exports.object({ outcome: external_exports.literal("recorded") }).strict();
|
|
16508
|
+
var observeRunKeptSchema = external_exports.object({ outcome: external_exports.literal("kept") }).strict();
|
|
16509
|
+
var observeRunNotSentSchema = external_exports.object({
|
|
16510
|
+
outcome: external_exports.literal("not-sent"),
|
|
16511
|
+
reason: external_exports.enum(OBSERVE_NOT_SENT_REASONS)
|
|
16512
|
+
}).strict();
|
|
16513
|
+
var observeRunResultSchema = external_exports.discriminatedUnion("outcome", [
|
|
16514
|
+
observeRunRecordedSchema,
|
|
16515
|
+
observeRunKeptSchema,
|
|
16516
|
+
observeRunNotSentSchema
|
|
16517
|
+
]);
|
|
16518
|
+
var observedChecksFiredSchema = external_exports.array(external_exports.string().min(1)).refine((names) => names.every((name, i) => i === 0 || names[i - 1] < name), {
|
|
16519
|
+
message: "must be sorted, each name once"
|
|
16520
|
+
});
|
|
16521
|
+
var observedCountingSchema = external_exports.discriminatedUnion("state", [
|
|
16522
|
+
external_exports.object({
|
|
16523
|
+
state: external_exports.literal("counted"),
|
|
16524
|
+
startCommit: closeCommitSchema,
|
|
16525
|
+
startRecordedAt: utcIso8601,
|
|
16526
|
+
headCommit: closeCommitSchema,
|
|
16527
|
+
headCommittedAt: utcIso8601,
|
|
16528
|
+
commits: observedCountSchema,
|
|
16529
|
+
countedAt: utcIso8601
|
|
16530
|
+
}).strict(),
|
|
16531
|
+
external_exports.object({
|
|
16532
|
+
state: external_exports.literal("not-counted-yet"),
|
|
16533
|
+
startCommit: closeCommitSchema,
|
|
16534
|
+
startRecordedAt: utcIso8601
|
|
16535
|
+
}).strict(),
|
|
16536
|
+
external_exports.object({
|
|
16537
|
+
state: external_exports.literal("cannot-count")
|
|
16538
|
+
}).strict()
|
|
16539
|
+
]);
|
|
16540
|
+
var observedPartSchema = external_exports.object({
|
|
16541
|
+
part: systemPartIdSchema,
|
|
16542
|
+
files: observedFilesSchema.nullable(),
|
|
16543
|
+
checksFired: observedChecksFiredSchema
|
|
16544
|
+
}).strict().refine((v) => v.files !== null && filesChanged(v.files) >= 1 || v.checksFired.length >= 1, {
|
|
16545
|
+
message: "a part is listed only when files changed under it or a check fired on one",
|
|
16546
|
+
path: ["files"]
|
|
16547
|
+
});
|
|
16548
|
+
var observedOutsideSchema = external_exports.object({
|
|
16549
|
+
files: observedFilesSchema.nullable(),
|
|
16550
|
+
paths: observedPathListSchema,
|
|
16551
|
+
checksFired: observedChecksFiredSchema
|
|
16552
|
+
}).strict().refine((v) => pathsFitTheCount(v.files, v.paths), {
|
|
16553
|
+
message: "lists more paths than files changed",
|
|
16554
|
+
path: ["paths"]
|
|
16555
|
+
});
|
|
16556
|
+
var observedChangesSchema = external_exports.object({
|
|
16557
|
+
phase: phaseNameSchema,
|
|
16558
|
+
counting: observedCountingSchema,
|
|
16559
|
+
descriptionVersion: observedDescriptionVersionSchema.nullable(),
|
|
16560
|
+
parts: external_exports.array(observedPartSchema).max(SYSTEM_PARTS_MAX),
|
|
16561
|
+
methodFiles: observedMethodFilesSchema.nullable(),
|
|
16562
|
+
outsideEveryPart: observedOutsideSchema,
|
|
16563
|
+
codeLink: codeLinkFactsSchema.nullable()
|
|
16564
|
+
}).strict().superRefine((v, ctx) => {
|
|
16565
|
+
checkPartsUnique(v.parts, ctx, ["parts"]);
|
|
16566
|
+
const counted = v.counting.state === "counted";
|
|
16567
|
+
const rule = counted ? "must be present when the phase is counted" : "must be null until the phase is counted";
|
|
16568
|
+
if (v.methodFiles !== null !== counted)
|
|
16569
|
+
ctx.addIssue({ code: "custom", path: ["methodFiles"], message: rule });
|
|
16570
|
+
if (v.outsideEveryPart.files !== null !== counted) {
|
|
16571
|
+
ctx.addIssue({ code: "custom", path: ["outsideEveryPart", "files"], message: rule });
|
|
16572
|
+
}
|
|
16573
|
+
if (v.codeLink !== null !== counted)
|
|
16574
|
+
ctx.addIssue({ code: "custom", path: ["codeLink"], message: rule });
|
|
16575
|
+
v.parts.forEach((entry, i) => {
|
|
16576
|
+
if (entry.files !== null !== counted)
|
|
16577
|
+
ctx.addIssue({ code: "custom", path: ["parts", i, "files"], message: rule });
|
|
16043
16578
|
});
|
|
16579
|
+
if (counted && v.descriptionVersion === null) {
|
|
16580
|
+
ctx.addIssue({ code: "custom", path: ["descriptionVersion"], message: "must name the version the counts used" });
|
|
16581
|
+
}
|
|
16044
16582
|
});
|
|
16045
|
-
var
|
|
16046
|
-
|
|
16047
|
-
|
|
16048
|
-
|
|
16583
|
+
var partOutsideThePlanSchema = external_exports.object({
|
|
16584
|
+
part: systemPartIdSchema,
|
|
16585
|
+
name: systemPartSchema.shape.name,
|
|
16586
|
+
seen: observedFilesSchema.nullable(),
|
|
16587
|
+
tasks: external_exports.array(observedSaidSchema.extend({ id: buildTaskSchema.shape.id }).strict()).max(BUILD_SNAPSHOT_TASK_LIMIT)
|
|
16049
16588
|
}).strict();
|
|
16050
|
-
var
|
|
16051
|
-
|
|
16052
|
-
|
|
16053
|
-
|
|
16054
|
-
}
|
|
16055
|
-
|
|
16589
|
+
var NO_TASK_SAID_LINE = "No task said it changed this part.";
|
|
16590
|
+
function agentSaid(task) {
|
|
16591
|
+
const { title, agent, whyOutsidePlan } = task;
|
|
16592
|
+
if (whyOutsidePlan === void 0)
|
|
16593
|
+
return `"${title}" said it built this part, and gave no reason.`;
|
|
16594
|
+
if (agent === HUMAN_AGENT)
|
|
16595
|
+
return `The person on "${title}" said: "${whyOutsidePlan}"`;
|
|
16596
|
+
if (agent === void 0)
|
|
16597
|
+
return `The agent on "${title}" said: "${whyOutsidePlan}"`;
|
|
16598
|
+
return `${agent} said, on "${title}": "${whyOutsidePlan}"`;
|
|
16599
|
+
}
|
|
16600
|
+
|
|
16601
|
+
// ../events/dist/phase-review.js
|
|
16602
|
+
var BUILT_PART_ACTS = Object.freeze(["accept", "question", "send-back"]);
|
|
16603
|
+
var NOT_BUILT_PART_ACTS = Object.freeze(["carry", "drop"]);
|
|
16604
|
+
var REVIEW_ACTS = Object.freeze([...BUILT_PART_ACTS, ...NOT_BUILT_PART_ACTS]);
|
|
16605
|
+
var reviewActSchema = external_exports.enum(REVIEW_ACTS);
|
|
16606
|
+
function reviewActsFor(built) {
|
|
16607
|
+
return built ? BUILT_PART_ACTS : NOT_BUILT_PART_ACTS;
|
|
16608
|
+
}
|
|
16609
|
+
var REVIEW_NOTE_MAX = SCOPE_NOTE_MAX;
|
|
16610
|
+
var reviewNoteWireSchema = external_exports.string().max(REVIEW_NOTE_MAX);
|
|
16611
|
+
var reviewNoteSchema = external_exports.string().min(1).max(REVIEW_NOTE_MAX);
|
|
16612
|
+
var REVIEW_ANSWER_MAX = 1e3;
|
|
16613
|
+
var reviewAgentAnswerSchema = external_exports.string().min(1).max(REVIEW_ANSWER_MAX);
|
|
16614
|
+
var REVIEW_PARTS_MAX = PHASE_PLAN_CHANGES_MAX * 2;
|
|
16615
|
+
var REVIEW_PART_CHANGES = Object.freeze([...PHASE_PLAN_CHANGE_KINDS, "not-in-plan"]);
|
|
16616
|
+
var reviewPartChangeSchema = external_exports.enum(REVIEW_PART_CHANGES);
|
|
16617
|
+
var REVIEW_DECISIONS = Object.freeze(["accepted", "carried", "dropped"]);
|
|
16618
|
+
var reviewDecisionSchema = external_exports.enum(REVIEW_DECISIONS);
|
|
16619
|
+
function actOf(decision) {
|
|
16620
|
+
if (decision === "accepted")
|
|
16621
|
+
return "accept";
|
|
16622
|
+
if (decision === "carried")
|
|
16623
|
+
return "carry";
|
|
16624
|
+
return "drop";
|
|
16625
|
+
}
|
|
16626
|
+
var reviewCarriedToSchema = carriedPhaseSchema.nullable();
|
|
16627
|
+
var reviewStepNameSchema = stepNameSchema.refine((v) => !stepIdSchema.safeParse(v.trim()).success, {
|
|
16628
|
+
message: "must be the step's plain name"
|
|
16056
16629
|
});
|
|
16057
|
-
|
|
16058
|
-
|
|
16630
|
+
var reviewStepSchema = external_exports.object({
|
|
16631
|
+
name: reviewStepNameSchema.nullable(),
|
|
16632
|
+
state: unfinishedStepSchema.shape.state,
|
|
16633
|
+
judged: external_exports.boolean()
|
|
16634
|
+
}).strict();
|
|
16635
|
+
var UNNAMED_STEP = "a step of this phase";
|
|
16636
|
+
function stepCalled(step) {
|
|
16637
|
+
return step.name === null ? UNNAMED_STEP : `"${step.name}"`;
|
|
16059
16638
|
}
|
|
16060
|
-
function
|
|
16061
|
-
|
|
16062
|
-
return TYPED_APPROVAL_OFF_SENTENCE;
|
|
16063
|
-
if (offerTyping === "update")
|
|
16064
|
-
return CLIENT_UPDATE_SENTENCE;
|
|
16065
|
-
return `In their own Claude Code session, they ask to see the plan, check each part, and then type: approve ${phase}`;
|
|
16639
|
+
function sentenceStart(text) {
|
|
16640
|
+
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
16066
16641
|
}
|
|
16067
|
-
function
|
|
16068
|
-
|
|
16069
|
-
|
|
16070
|
-
...entry.notes.map((n) => n.part === void 0 ? `- ${n.note}` : `- "${n.part}": ${n.note}`),
|
|
16071
|
-
`Change the plan to answer every note, then send it again with \`${HALFCYCLE_COMMAND} plan ${quoteForShell(entry.phase)}\`.`
|
|
16072
|
-
].join("\n");
|
|
16642
|
+
function unfinishedStepLabel(step) {
|
|
16643
|
+
const state = step.state === "parked" ? "parked" : "attempted, and did not finish";
|
|
16644
|
+
return `${stepCalled(step)} (${state})`;
|
|
16073
16645
|
}
|
|
16074
|
-
function
|
|
16075
|
-
|
|
16076
|
-
|
|
16077
|
-
|
|
16078
|
-
|
|
16079
|
-
].join(" ");
|
|
16646
|
+
function unfinishedStepLine(step) {
|
|
16647
|
+
if (step.judged) {
|
|
16648
|
+
return `${sentenceStart(stepCalled(step))} is judged by Halfcycle and has not passed in this phase: run that review again in your coding session until it passes.`;
|
|
16649
|
+
}
|
|
16650
|
+
return `Finish ${stepCalled(step)} in your coding session, or record it as declined.`;
|
|
16080
16651
|
}
|
|
16081
|
-
|
|
16082
|
-
|
|
16083
|
-
|
|
16084
|
-
|
|
16085
|
-
|
|
16086
|
-
|
|
16652
|
+
var openReviewRequestSchema = external_exports.object({
|
|
16653
|
+
actor: closeActorSchema,
|
|
16654
|
+
acceptanceOutcome: acceptanceOutcomeSchema,
|
|
16655
|
+
statements: closeStatementsSchema
|
|
16656
|
+
}).strict();
|
|
16657
|
+
var REVIEW_OPEN_TASKS_SHOWN_MAX = 10;
|
|
16658
|
+
var reviewOpenedSchema = external_exports.object({
|
|
16659
|
+
outcome: external_exports.literal("opened"),
|
|
16660
|
+
round: external_exports.number().int().min(1),
|
|
16661
|
+
parts: external_exports.number().int().min(0).max(REVIEW_PARTS_MAX),
|
|
16662
|
+
notBuilt: external_exports.number().int().min(0).max(REVIEW_PARTS_MAX),
|
|
16663
|
+
counted: external_exports.boolean(),
|
|
16664
|
+
typedReviewOffered: external_exports.boolean()
|
|
16665
|
+
}).strict().refine((v) => v.notBuilt <= v.parts, { message: "cannot exceed the parts in the round", path: ["notBuilt"] });
|
|
16666
|
+
var reviewTasksOpenSchema = external_exports.object({
|
|
16667
|
+
outcome: external_exports.literal("tasks-open"),
|
|
16668
|
+
open: external_exports.array(buildTaskSchema.shape.title).min(1).max(REVIEW_OPEN_TASKS_SHOWN_MAX),
|
|
16669
|
+
more: external_exports.number().int().min(0).max(BUILD_SNAPSHOT_TASK_LIMIT)
|
|
16670
|
+
}).strict();
|
|
16671
|
+
var reviewOutstandingSchema = external_exports.object({
|
|
16672
|
+
outcome: external_exports.literal("outstanding"),
|
|
16673
|
+
steps: external_exports.array(reviewStepSchema).min(1)
|
|
16674
|
+
}).strict();
|
|
16675
|
+
var reviewCleanOverFindingsSchema = external_exports.object({
|
|
16676
|
+
outcome: external_exports.literal("clean-over-findings"),
|
|
16677
|
+
findings: external_exports.number().int().min(1)
|
|
16678
|
+
}).strict();
|
|
16679
|
+
var reviewRefusedSchema = external_exports.object({
|
|
16680
|
+
outcome: external_exports.literal("refused"),
|
|
16681
|
+
problems: external_exports.array(systemDescriptionProblemSchema).min(1)
|
|
16682
|
+
}).strict();
|
|
16683
|
+
function bareReviewOutcome(outcome) {
|
|
16684
|
+
return external_exports.object({ outcome: external_exports.literal(outcome) }).strict();
|
|
16685
|
+
}
|
|
16686
|
+
var openReviewResultSchema = external_exports.discriminatedUnion("outcome", [
|
|
16687
|
+
reviewOpenedSchema,
|
|
16688
|
+
reviewTasksOpenSchema,
|
|
16689
|
+
bareReviewOutcome("no-build"),
|
|
16690
|
+
bareReviewOutcome("review-open"),
|
|
16691
|
+
reviewOutstandingSchema,
|
|
16692
|
+
reviewCleanOverFindingsSchema,
|
|
16693
|
+
reviewRefusedSchema,
|
|
16694
|
+
bareReviewOutcome("unavailable")
|
|
16695
|
+
]);
|
|
16696
|
+
var reviewPhaseNotOpenSchema = external_exports.object({
|
|
16697
|
+
statusCode: external_exports.literal(409),
|
|
16698
|
+
error: external_exports.literal("PhaseNotOpen"),
|
|
16699
|
+
message: external_exports.string().min(1),
|
|
16700
|
+
openPhase: external_exports.string().nullable()
|
|
16701
|
+
}).strict();
|
|
16702
|
+
var openReviewAnswerSchema = external_exports.union([openReviewResultSchema, reviewPhaseNotOpenSchema]);
|
|
16703
|
+
var reviewDecisionEntrySchema = external_exports.object({
|
|
16704
|
+
part: systemPartIdSchema,
|
|
16705
|
+
act: reviewActSchema,
|
|
16706
|
+
note: reviewNoteWireSchema.optional()
|
|
16707
|
+
}).strict();
|
|
16708
|
+
var reviewDecisionRequestSchema = external_exports.object({
|
|
16709
|
+
reviewVersion: external_exports.number().int().min(0),
|
|
16710
|
+
decisions: external_exports.array(reviewDecisionEntrySchema).max(REVIEW_PARTS_MAX)
|
|
16711
|
+
}).strict();
|
|
16712
|
+
var reviewDecidedSchema = external_exports.object({
|
|
16713
|
+
part: systemPartIdSchema,
|
|
16714
|
+
name: systemPartSchema.shape.name,
|
|
16715
|
+
act: reviewActSchema,
|
|
16716
|
+
carriedTo: reviewCarriedToSchema.optional()
|
|
16717
|
+
}).strict().refine((v) => v.carriedTo !== void 0 === (v.act === "carry"), {
|
|
16718
|
+
message: "is present exactly on a carry",
|
|
16719
|
+
path: ["carriedTo"]
|
|
16720
|
+
});
|
|
16721
|
+
var reviewRecordedSchema = external_exports.object({
|
|
16722
|
+
outcome: external_exports.literal("recorded"),
|
|
16723
|
+
decided: external_exports.array(reviewDecidedSchema).max(REVIEW_PARTS_MAX),
|
|
16724
|
+
waiting: external_exports.number().int().min(0).max(REVIEW_PARTS_MAX),
|
|
16725
|
+
closed: external_exports.boolean(),
|
|
16726
|
+
reviewVersion: external_exports.number().int().min(1)
|
|
16727
|
+
}).strict().superRefine((v, ctx) => {
|
|
16728
|
+
if (v.closed && v.waiting !== 0) {
|
|
16729
|
+
ctx.addIssue({ code: "custom", path: ["waiting"], message: "must be 0 once the phase is closed" });
|
|
16730
|
+
}
|
|
16731
|
+
if (v.decided.length === 0 && !v.closed) {
|
|
16732
|
+
ctx.addIssue({ code: "custom", path: ["decided"], message: "may be empty only when the phase closed" });
|
|
16733
|
+
}
|
|
16734
|
+
});
|
|
16735
|
+
var REVIEW_NOT_DECIDABLE_WHY = Object.freeze(["settled", "question-waiting", "sent-back"]);
|
|
16736
|
+
var reviewNotDecidablePartSchema = external_exports.object({
|
|
16737
|
+
part: systemPartIdSchema,
|
|
16738
|
+
name: systemPartSchema.shape.name,
|
|
16739
|
+
why: external_exports.enum(REVIEW_NOT_DECIDABLE_WHY)
|
|
16740
|
+
}).strict();
|
|
16741
|
+
var reviewNotDecidableSchema = external_exports.object({
|
|
16742
|
+
outcome: external_exports.literal("not-decidable"),
|
|
16743
|
+
parts: external_exports.array(reviewNotDecidablePartSchema).min(1).max(REVIEW_PARTS_MAX)
|
|
16744
|
+
}).strict();
|
|
16745
|
+
var reviewCloseRefusedSchema = external_exports.object({
|
|
16746
|
+
outcome: external_exports.literal("close-refused"),
|
|
16747
|
+
steps: external_exports.array(reviewStepSchema)
|
|
16748
|
+
}).strict();
|
|
16749
|
+
var reviewWalkChangedSchema = external_exports.object({
|
|
16750
|
+
outcome: external_exports.literal("walk-changed"),
|
|
16751
|
+
findings: external_exports.number().int().min(1)
|
|
16752
|
+
}).strict();
|
|
16753
|
+
var reviewDecisionResultSchema = external_exports.discriminatedUnion("outcome", [
|
|
16754
|
+
reviewRecordedSchema,
|
|
16755
|
+
bareReviewOutcome("review-changed"),
|
|
16756
|
+
bareReviewOutcome("no-review"),
|
|
16757
|
+
reviewNotDecidableSchema,
|
|
16758
|
+
reviewCloseRefusedSchema,
|
|
16759
|
+
reviewWalkChangedSchema,
|
|
16760
|
+
bareReviewOutcome("not-owner"),
|
|
16761
|
+
bareReviewOutcome("no-owner"),
|
|
16762
|
+
reviewRefusedSchema,
|
|
16763
|
+
bareReviewOutcome("unavailable")
|
|
16764
|
+
]);
|
|
16765
|
+
var reviewSentBackSchema = external_exports.object({
|
|
16766
|
+
round: external_exports.number().int().min(1),
|
|
16767
|
+
note: reviewNoteSchema
|
|
16768
|
+
}).strict();
|
|
16769
|
+
var reviewQuestionSchema = external_exports.object({
|
|
16770
|
+
question: reviewNoteSchema,
|
|
16771
|
+
agentAnswer: reviewAgentAnswerSchema.optional()
|
|
16772
|
+
}).strict();
|
|
16773
|
+
var reviewTaskSchema = external_exports.object({
|
|
16774
|
+
title: buildTaskSchema.shape.title,
|
|
16775
|
+
state: buildTaskStateSchema
|
|
16776
|
+
}).strict();
|
|
16777
|
+
var reviewViewPartSchema = external_exports.object({
|
|
16778
|
+
part: systemPartIdSchema,
|
|
16779
|
+
name: systemPartSchema.shape.name,
|
|
16780
|
+
change: reviewPartChangeSchema,
|
|
16781
|
+
built: external_exports.boolean(),
|
|
16782
|
+
builtWithoutApproval: external_exports.boolean(),
|
|
16783
|
+
decidable: external_exports.boolean(),
|
|
16784
|
+
waiting: external_exports.boolean(),
|
|
16785
|
+
decision: reviewDecisionSchema.optional(),
|
|
16786
|
+
reason: reviewNoteSchema.optional(),
|
|
16787
|
+
carriedTo: reviewCarriedToSchema.optional(),
|
|
16788
|
+
sentBack: external_exports.array(reviewSentBackSchema),
|
|
16789
|
+
questions: external_exports.array(reviewQuestionSchema),
|
|
16790
|
+
tasks: external_exports.array(reviewTaskSchema).max(BUILD_SNAPSHOT_TASK_LIMIT),
|
|
16791
|
+
said: external_exports.array(observedSaidSchema).max(BUILD_SNAPSHOT_TASK_LIMIT),
|
|
16792
|
+
codeLink: partCodeLinkSchema
|
|
16793
|
+
}).strict().superRefine((v, ctx) => {
|
|
16794
|
+
if (v.waiting !== (v.decision === void 0)) {
|
|
16795
|
+
ctx.addIssue({ code: "custom", path: ["waiting"], message: "must be true exactly when the part has no decision" });
|
|
16796
|
+
}
|
|
16797
|
+
if (v.decidable && !v.waiting) {
|
|
16798
|
+
ctx.addIssue({ code: "custom", path: ["decidable"], message: "cannot be true for a settled part" });
|
|
16799
|
+
}
|
|
16800
|
+
if (v.decision !== void 0 && !reviewActsFor(v.built).includes(actOf(v.decision))) {
|
|
16801
|
+
ctx.addIssue({ code: "custom", path: ["decision"], message: "is not an act this part takes" });
|
|
16802
|
+
}
|
|
16803
|
+
const carriedOrDropped = v.decision === "carried" || v.decision === "dropped";
|
|
16804
|
+
if (v.reason !== void 0 !== carriedOrDropped) {
|
|
16805
|
+
ctx.addIssue({ code: "custom", path: ["reason"], message: "is present exactly on a carry or a drop" });
|
|
16806
|
+
}
|
|
16807
|
+
if (v.carriedTo !== void 0 !== (v.decision === "carried")) {
|
|
16808
|
+
ctx.addIssue({ code: "custom", path: ["carriedTo"], message: "is present exactly on a carry" });
|
|
16809
|
+
}
|
|
16810
|
+
if (v.change === "not-in-plan" && !v.built) {
|
|
16811
|
+
ctx.addIssue({ code: "custom", path: ["built"], message: "is always true for a part outside the plan" });
|
|
16812
|
+
}
|
|
16813
|
+
});
|
|
16814
|
+
var reviewPhaseNumberSchema = external_exports.number().int().min(1).nullable();
|
|
16815
|
+
var phaseReviewNoneSchema = external_exports.object({
|
|
16816
|
+
state: external_exports.literal("none"),
|
|
16817
|
+
phase: scopePhaseSchema,
|
|
16818
|
+
number: reviewPhaseNumberSchema,
|
|
16819
|
+
closedWithoutReview: external_exports.boolean()
|
|
16820
|
+
}).strict();
|
|
16821
|
+
var reviewWalkSchema = external_exports.object({
|
|
16822
|
+
verdict: walkVerdictSchema,
|
|
16823
|
+
findings: external_exports.number().int().min(0)
|
|
16824
|
+
}).strict();
|
|
16825
|
+
function reviewedViewSchema(state) {
|
|
16826
|
+
return external_exports.object({
|
|
16827
|
+
state: external_exports.literal(state),
|
|
16828
|
+
phase: scopePhaseSchema,
|
|
16829
|
+
number: reviewPhaseNumberSchema,
|
|
16830
|
+
cycle: external_exports.number().int().min(0),
|
|
16831
|
+
round: external_exports.number().int().min(1),
|
|
16832
|
+
reviewVersion: external_exports.number().int().min(1),
|
|
16833
|
+
typedReviewOffered: external_exports.boolean(),
|
|
16834
|
+
counted: external_exports.boolean(),
|
|
16835
|
+
walk: reviewWalkSchema,
|
|
16836
|
+
parts: external_exports.array(reviewViewPartSchema).max(REVIEW_PARTS_MAX),
|
|
16837
|
+
observed: observedChangesSchema.nullable(),
|
|
16838
|
+
description: systemDescriptionSchema
|
|
16839
|
+
}).strict().superRefine((v, ctx) => {
|
|
16840
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16841
|
+
v.parts.forEach((p, i) => {
|
|
16842
|
+
if (seen.has(p.part))
|
|
16843
|
+
ctx.addIssue({ code: "custom", path: ["parts", i, "part"], message: "is listed twice" });
|
|
16844
|
+
seen.add(p.part);
|
|
16845
|
+
});
|
|
16846
|
+
const counted = v.observed !== null && v.observed.counting.state === "counted";
|
|
16847
|
+
if (v.counted !== counted) {
|
|
16848
|
+
ctx.addIssue({ code: "custom", path: ["counted"], message: "must be true exactly when the counts are counted" });
|
|
16849
|
+
}
|
|
16850
|
+
if (v.observed !== null && v.observed.phase !== v.phase) {
|
|
16851
|
+
ctx.addIssue({ code: "custom", path: ["observed", "phase"], message: "must be the phase under review" });
|
|
16852
|
+
}
|
|
16853
|
+
if (v.state === "closed") {
|
|
16854
|
+
v.parts.forEach((p, i) => {
|
|
16855
|
+
if (p.decision === void 0) {
|
|
16856
|
+
ctx.addIssue({ code: "custom", path: ["parts", i, "decision"], message: "every part of a closed review is decided" });
|
|
16857
|
+
}
|
|
16858
|
+
});
|
|
16859
|
+
}
|
|
16860
|
+
});
|
|
16087
16861
|
}
|
|
16862
|
+
var phaseReviewOpenSchema = reviewedViewSchema("open");
|
|
16863
|
+
var phaseReviewClosedSchema = reviewedViewSchema("closed");
|
|
16864
|
+
var phaseReviewViewSchema = external_exports.discriminatedUnion("state", [
|
|
16865
|
+
phaseReviewNoneSchema,
|
|
16866
|
+
phaseReviewOpenSchema,
|
|
16867
|
+
phaseReviewClosedSchema
|
|
16868
|
+
]);
|
|
16869
|
+
|
|
16870
|
+
// ../events/dist/client-version.js
|
|
16871
|
+
var CLIENT_VERSION_HEADER = "x-halfcycle-client";
|
|
16872
|
+
var TYPED_APPROVAL_OFF_SENTENCE = "Approving by typing is switched off for now.";
|
|
16873
|
+
var TYPED_REVIEW_OFF_SENTENCE = "Reviewing by typing is switched off for now.";
|
|
16874
|
+
var CLIENT_UPDATE_SENTENCE = `To approve by typing in a session, Halfcycle needs updating: run \`${HALFCYCLE_COMMAND}\` in this project's folder, then reopen your session and ask again.`;
|
|
16088
16875
|
|
|
16089
16876
|
// ../events/dist/device-auth.js
|
|
16090
16877
|
var utcIso86012 = external_exports.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/, "must be a UTC ISO-8601 timestamp ending in Z");
|
|
@@ -16185,11 +16972,126 @@ var DASHBOARD_URL = "https://app.halfcycle.ai";
|
|
|
16185
16972
|
function projectPageUrl(engagementId) {
|
|
16186
16973
|
return isEngagementId(engagementId) ? `${DASHBOARD_URL}/${encodeURIComponent(engagementId)}` : void 0;
|
|
16187
16974
|
}
|
|
16975
|
+
function systemViewUrl(engagementId, phase) {
|
|
16976
|
+
const page = projectPageUrl(engagementId);
|
|
16977
|
+
if (page === void 0)
|
|
16978
|
+
return void 0;
|
|
16979
|
+
return `${page}/console/system-view?${new URLSearchParams({ phase }).toString()}`;
|
|
16980
|
+
}
|
|
16188
16981
|
function requestAccessSentence(engagementId) {
|
|
16189
16982
|
const page = projectPageUrl(engagementId) ?? "this project's page on Halfcycle";
|
|
16190
16983
|
return `To ask for access, open ${page} signed in as this account and choose "Request access". Then let the project's owner know, so they can approve it.`;
|
|
16191
16984
|
}
|
|
16192
16985
|
|
|
16986
|
+
// ../events/dist/scope-notices.js
|
|
16987
|
+
var scopePartNameSchema = external_exports.string().min(1).max(SYSTEM_PART_NAME_MAX);
|
|
16988
|
+
var scopeNoteEntrySchema = external_exports.object({
|
|
16989
|
+
part: scopePartNameSchema.optional(),
|
|
16990
|
+
note: external_exports.string().min(1).max(SCOPE_NOTE_MAX)
|
|
16991
|
+
}).strict();
|
|
16992
|
+
var scopeSentBackSchema = external_exports.object({
|
|
16993
|
+
phase: scopePhaseSchema,
|
|
16994
|
+
number: external_exports.number().int().min(1),
|
|
16995
|
+
notes: external_exports.array(scopeNoteEntrySchema).min(1).max(PHASE_PLAN_CHANGES_MAX + 1)
|
|
16996
|
+
}).strict().superRefine((entry, ctx) => {
|
|
16997
|
+
entry.notes.forEach((n, i) => {
|
|
16998
|
+
if (n.part === void 0 && i !== entry.notes.length - 1) {
|
|
16999
|
+
ctx.addIssue({
|
|
17000
|
+
code: external_exports.ZodIssueCode.custom,
|
|
17001
|
+
path: ["notes", i],
|
|
17002
|
+
message: "the note on the whole phase comes after every note on a part"
|
|
17003
|
+
});
|
|
17004
|
+
}
|
|
17005
|
+
});
|
|
17006
|
+
});
|
|
17007
|
+
var scopeWaitingSchema = external_exports.object({
|
|
17008
|
+
phase: scopePhaseSchema,
|
|
17009
|
+
number: external_exports.number().int().min(1),
|
|
17010
|
+
changedParts: external_exports.array(scopePartNameSchema).max(PHASE_PLAN_CHANGES_MAX)
|
|
17011
|
+
}).strict();
|
|
17012
|
+
var scopeNoticesSchema = external_exports.object({
|
|
17013
|
+
sentBack: external_exports.array(scopeSentBackSchema),
|
|
17014
|
+
waiting: external_exports.array(scopeWaitingSchema),
|
|
17015
|
+
typedApprovalOffered: external_exports.boolean()
|
|
17016
|
+
}).strict().refine((v) => v.sentBack.length > 0 || v.waiting.length > 0, {
|
|
17017
|
+
message: "carried only when a phase was sent back or waits for the owner"
|
|
17018
|
+
});
|
|
17019
|
+
function quotedParts(parts) {
|
|
17020
|
+
return parts.map((p) => `"${p}"`).join(", ");
|
|
17021
|
+
}
|
|
17022
|
+
function approvalSentence(phase, offerTyping) {
|
|
17023
|
+
if (offerTyping === "off")
|
|
17024
|
+
return TYPED_APPROVAL_OFF_SENTENCE;
|
|
17025
|
+
if (offerTyping === "update")
|
|
17026
|
+
return CLIENT_UPDATE_SENTENCE;
|
|
17027
|
+
return `In their own Claude Code session, they ask to see the plan, check each part, and then type: approve ${phase}`;
|
|
17028
|
+
}
|
|
17029
|
+
function phaseWords(phase) {
|
|
17030
|
+
return `phase "${phase}"`;
|
|
17031
|
+
}
|
|
17032
|
+
function sentBackNotice(entry) {
|
|
17033
|
+
return [
|
|
17034
|
+
`Before anything else: the project's owner sent the scope of ${phaseWords(entry.phase)} back with these notes.`,
|
|
17035
|
+
...entry.notes.map((n) => n.part === void 0 ? `- ${n.note}` : `- "${n.part}": ${n.note}`),
|
|
17036
|
+
`Change the plan to answer every note, then send it again: say "plan the next phase" to start. It then waits for the owner's approval again.`
|
|
17037
|
+
].join("\n");
|
|
17038
|
+
}
|
|
17039
|
+
function systemViewSentence(offerTyping, address) {
|
|
17040
|
+
if (address === void 0)
|
|
17041
|
+
return [];
|
|
17042
|
+
return [offerTyping === "yes" ? `Or they approve it in the System view: ${address}` : `They approve it in the System view: ${address}`];
|
|
17043
|
+
}
|
|
17044
|
+
function waitingNotice(entry, offerTyping, engagementId) {
|
|
17045
|
+
return [
|
|
17046
|
+
...entry.changedParts.length > 0 ? [`These parts changed since the owner last decided on this phase, and wait for their approval: ${quotedParts(entry.changedParts)}.`] : [],
|
|
17047
|
+
`The scope of ${phaseWords(entry.phase)} is waiting for the project's owner to approve it.`,
|
|
17048
|
+
approvalSentence(entry.phase, offerTyping),
|
|
17049
|
+
...systemViewSentence(offerTyping, systemViewUrl(engagementId, entry.phase))
|
|
17050
|
+
].join(" ");
|
|
17051
|
+
}
|
|
17052
|
+
function scopeNoticeText(scope, options) {
|
|
17053
|
+
const omit2 = options.omitActivePhase === true ? options.activePhase : void 0;
|
|
17054
|
+
return {
|
|
17055
|
+
sentBack: scope.sentBack.map(sentBackNotice),
|
|
17056
|
+
waiting: scope.waiting.filter((w) => omit2 === void 0 || w.phase !== omit2).map((w) => waitingNotice(w, options.offerTyping, options.engagementId))
|
|
17057
|
+
};
|
|
17058
|
+
}
|
|
17059
|
+
|
|
17060
|
+
// ../events/dist/review-notices.js
|
|
17061
|
+
var reviewNoticeQuestionSchema = external_exports.object({
|
|
17062
|
+
phase: scopePhaseSchema,
|
|
17063
|
+
number: reviewPhaseNumberSchema,
|
|
17064
|
+
part: systemPartIdSchema,
|
|
17065
|
+
partName: systemPartSchema.shape.name,
|
|
17066
|
+
question: reviewNoteSchema
|
|
17067
|
+
}).strict();
|
|
17068
|
+
var reviewNoticeSentBackSchema = external_exports.object({
|
|
17069
|
+
phase: scopePhaseSchema,
|
|
17070
|
+
number: reviewPhaseNumberSchema,
|
|
17071
|
+
part: systemPartIdSchema,
|
|
17072
|
+
partName: systemPartSchema.shape.name,
|
|
17073
|
+
note: reviewNoteSchema
|
|
17074
|
+
}).strict();
|
|
17075
|
+
var reviewNoticesSchema = external_exports.object({
|
|
17076
|
+
questions: external_exports.array(reviewNoticeQuestionSchema).max(REVIEW_PARTS_MAX),
|
|
17077
|
+
sentBack: external_exports.array(reviewNoticeSentBackSchema).max(REVIEW_PARTS_MAX)
|
|
17078
|
+
}).strict().refine((v) => v.questions.length > 0 || v.sentBack.length > 0, {
|
|
17079
|
+
message: "carried only when a question waits or a part was sent back"
|
|
17080
|
+
});
|
|
17081
|
+
function phaseCalled(phase) {
|
|
17082
|
+
return `phase "${phase}"`;
|
|
17083
|
+
}
|
|
17084
|
+
function questionNotice(entry) {
|
|
17085
|
+
const answer = `${HALFCYCLE_COMMAND} review ${quoteForShell(entry.phase)} --answer ${quoteForShell(entry.part)} "<your answer>"`;
|
|
17086
|
+
return `Before anything else: the project's owner asked about "${entry.partName}" in the review of ${phaseCalled(entry.phase)}: ${entry.question} Answer it with ${answer}.`;
|
|
17087
|
+
}
|
|
17088
|
+
function sentBackNotice2(entry) {
|
|
17089
|
+
return `Before anything else: the project's owner sent "${entry.partName}" in ${phaseCalled(entry.phase)} back to building: ${entry.note} Rebuild it: add or reopen the tasks that build it. For each task that rebuilds it, add a task that reviews the rebuild, held by a different agent from the one that built it. When each review has passed, report the build with the time each task finished, then run the close step again.`;
|
|
17090
|
+
}
|
|
17091
|
+
function reviewNoticeText(review) {
|
|
17092
|
+
return [...review.questions.map(questionNotice), ...review.sentBack.map(sentBackNotice2)];
|
|
17093
|
+
}
|
|
17094
|
+
|
|
16193
17095
|
// ../events/dist/credential-wire.js
|
|
16194
17096
|
var engagementCredentialResponseSchema = external_exports.object({
|
|
16195
17097
|
engagementId: external_exports.string(),
|
|
@@ -16242,7 +17144,7 @@ var chatScopeRequestSchema = external_exports.discriminatedUnion("act", [
|
|
|
16242
17144
|
chatScopeApproveRequestSchema,
|
|
16243
17145
|
chatScopeSendBackRequestSchema
|
|
16244
17146
|
]);
|
|
16245
|
-
var CHAT_SCOPE_ACTS =
|
|
17147
|
+
var CHAT_SCOPE_ACTS = SCOPE_ACTS;
|
|
16246
17148
|
var chatScopeRecordedSchema = external_exports.object({
|
|
16247
17149
|
outcome: external_exports.literal("recorded"),
|
|
16248
17150
|
act: external_exports.enum(CHAT_SCOPE_ACTS),
|
|
@@ -16273,6 +17175,56 @@ var chatScopeResultSchema = external_exports.discriminatedUnion("outcome", [
|
|
|
16273
17175
|
bareOutcome("unavailable")
|
|
16274
17176
|
]);
|
|
16275
17177
|
|
|
17178
|
+
// ../events/dist/review-chat.js
|
|
17179
|
+
var chatReviewAcceptRequestSchema = external_exports.object({
|
|
17180
|
+
act: external_exports.literal("accept"),
|
|
17181
|
+
reference: phaseIdentitySchema,
|
|
17182
|
+
reviewVersion: external_exports.number().int().min(0),
|
|
17183
|
+
parts: external_exports.array(systemPartIdSchema).max(REVIEW_PARTS_MAX)
|
|
17184
|
+
}).strict();
|
|
17185
|
+
function notedRequest(act) {
|
|
17186
|
+
return external_exports.object({
|
|
17187
|
+
act: external_exports.literal(act),
|
|
17188
|
+
reference: phaseIdentitySchema,
|
|
17189
|
+
reviewVersion: external_exports.number().int().min(0),
|
|
17190
|
+
part: systemPartIdSchema,
|
|
17191
|
+
note: reviewNoteWireSchema
|
|
17192
|
+
}).strict();
|
|
17193
|
+
}
|
|
17194
|
+
var chatReviewQuestionRequestSchema = notedRequest("question");
|
|
17195
|
+
var chatReviewSendBackRequestSchema = notedRequest("send-back");
|
|
17196
|
+
var chatReviewCarryRequestSchema = notedRequest("carry");
|
|
17197
|
+
var chatReviewDropRequestSchema = notedRequest("drop");
|
|
17198
|
+
var chatReviewRequestSchema = external_exports.discriminatedUnion("act", [
|
|
17199
|
+
chatReviewAcceptRequestSchema,
|
|
17200
|
+
chatReviewQuestionRequestSchema,
|
|
17201
|
+
chatReviewSendBackRequestSchema,
|
|
17202
|
+
chatReviewCarryRequestSchema,
|
|
17203
|
+
chatReviewDropRequestSchema
|
|
17204
|
+
]);
|
|
17205
|
+
function bareOutcome2(outcome) {
|
|
17206
|
+
return external_exports.object({ outcome: external_exports.literal(outcome) }).strict();
|
|
17207
|
+
}
|
|
17208
|
+
var chatReviewResultSchema = external_exports.discriminatedUnion("outcome", [
|
|
17209
|
+
...reviewDecisionResultSchema.options,
|
|
17210
|
+
bareOutcome2("show-first"),
|
|
17211
|
+
bareOutcome2("not-offered")
|
|
17212
|
+
]);
|
|
17213
|
+
var reviewAnswerRequestSchema = external_exports.object({
|
|
17214
|
+
part: systemPartIdSchema,
|
|
17215
|
+
answer: statementText(REVIEW_ANSWER_MAX)
|
|
17216
|
+
}).strict();
|
|
17217
|
+
var reviewAnswerRecordedSchema = external_exports.object({
|
|
17218
|
+
outcome: external_exports.literal("recorded"),
|
|
17219
|
+
name: systemPartSchema.shape.name
|
|
17220
|
+
}).strict();
|
|
17221
|
+
var reviewAnswerResultSchema = external_exports.discriminatedUnion("outcome", [
|
|
17222
|
+
reviewAnswerRecordedSchema,
|
|
17223
|
+
bareOutcome2("no-question"),
|
|
17224
|
+
reviewRefusedSchema,
|
|
17225
|
+
bareOutcome2("unavailable")
|
|
17226
|
+
]);
|
|
17227
|
+
|
|
16276
17228
|
// dist/hook-protocol.js
|
|
16277
17229
|
import { writeSync } from "node:fs";
|
|
16278
17230
|
function emitBlock(reason) {
|
|
@@ -16332,8 +17284,6 @@ function emitNeverConfigured(exitCode, reason, engagementId) {
|
|
|
16332
17284
|
writeSync(2, neverConfiguredReport(reason, void 0, engagementId));
|
|
16333
17285
|
process.exitCode = exitCode;
|
|
16334
17286
|
}
|
|
16335
|
-
var SIGN_IN_COMMAND = '"npx halfcycle"';
|
|
16336
|
-
var HOOK_REMEDY = `Run ${SIGN_IN_COMMAND} in the project folder to sign in.`;
|
|
16337
17287
|
function namesTheSignInCommand(text) {
|
|
16338
17288
|
return text.includes(SIGN_IN_COMMAND);
|
|
16339
17289
|
}
|
|
@@ -16660,13 +17610,16 @@ import { spawnSync } from "node:child_process";
|
|
|
16660
17610
|
import { existsSync, readFileSync } from "node:fs";
|
|
16661
17611
|
import { relative } from "node:path";
|
|
16662
17612
|
var COLOR_AWARE_SUBCOMMANDS = /* @__PURE__ */ new Set(["diff", "show", "log"]);
|
|
16663
|
-
function
|
|
17613
|
+
function spawnGit(args2, cwd, timeoutMs) {
|
|
16664
17614
|
const argv = COLOR_AWARE_SUBCOMMANDS.has(args2[0] ?? "") ? [args2[0], "--no-color", ...args2.slice(1)] : args2;
|
|
16665
|
-
|
|
17615
|
+
return spawnSync("git", argv, {
|
|
16666
17616
|
cwd,
|
|
16667
17617
|
encoding: "utf-8",
|
|
16668
17618
|
...timeoutMs === void 0 ? {} : { timeout: timeoutMs }
|
|
16669
17619
|
});
|
|
17620
|
+
}
|
|
17621
|
+
function git(args2, cwd, timeoutMs) {
|
|
17622
|
+
const result = spawnGit(args2, cwd, timeoutMs);
|
|
16670
17623
|
if (result.error) {
|
|
16671
17624
|
const missing = result.error.code === "ENOENT" && existsSync(cwd);
|
|
16672
17625
|
return { ok: false, message: `git error: ${result.error.message}`, ...missing ? { gitMissing: true } : {} };
|
|
@@ -16828,6 +17781,124 @@ function getWiringStatus(pathspecs, cwd) {
|
|
|
16828
17781
|
...pathspecs
|
|
16829
17782
|
], cwd, WIRING_STATUS_TIMEOUT_MS);
|
|
16830
17783
|
}
|
|
17784
|
+
var OBSERVE_GIT_TIMEOUT_MS = 5e3;
|
|
17785
|
+
function isFullCommit(value) {
|
|
17786
|
+
return closeCommitSchema.safeParse(value).success;
|
|
17787
|
+
}
|
|
17788
|
+
function notACommit(name) {
|
|
17789
|
+
return { ok: false, message: `${name} is not a full commit id` };
|
|
17790
|
+
}
|
|
17791
|
+
function headCommit(repoRoot) {
|
|
17792
|
+
const result = git(["rev-parse", "--verify", "HEAD^{commit}"], repoRoot, OBSERVE_GIT_TIMEOUT_MS);
|
|
17793
|
+
if (!result.ok)
|
|
17794
|
+
return result;
|
|
17795
|
+
const commit = result.value.trim();
|
|
17796
|
+
return isFullCommit(commit) ? { ok: true, value: commit } : notACommit("HEAD");
|
|
17797
|
+
}
|
|
17798
|
+
function headCommittedAt(head, repoRoot) {
|
|
17799
|
+
if (!isFullCommit(head))
|
|
17800
|
+
return notACommit("head");
|
|
17801
|
+
const result = git(["log", "-1", "--format=%cI", head], repoRoot, OBSERVE_GIT_TIMEOUT_MS);
|
|
17802
|
+
if (!result.ok)
|
|
17803
|
+
return result;
|
|
17804
|
+
const when = new Date(result.value.trim());
|
|
17805
|
+
if (Number.isNaN(when.getTime()))
|
|
17806
|
+
return { ok: false, message: "git gave no readable commit time" };
|
|
17807
|
+
const iso = when.toISOString();
|
|
17808
|
+
return utcIso8601.safeParse(iso).success ? { ok: true, value: iso } : { ok: false, message: "git gave no readable commit time" };
|
|
17809
|
+
}
|
|
17810
|
+
function isAncestor(start, head, repoRoot) {
|
|
17811
|
+
if (!isFullCommit(start))
|
|
17812
|
+
return notACommit("start");
|
|
17813
|
+
if (!isFullCommit(head))
|
|
17814
|
+
return notACommit("head");
|
|
17815
|
+
const result = spawnGit(["merge-base", "--is-ancestor", start, head], repoRoot, OBSERVE_GIT_TIMEOUT_MS);
|
|
17816
|
+
if (result.error)
|
|
17817
|
+
return { ok: false, message: `git error: ${result.error.message}` };
|
|
17818
|
+
if (result.status === 0)
|
|
17819
|
+
return { ok: true, value: true };
|
|
17820
|
+
if (result.status === 1)
|
|
17821
|
+
return { ok: true, value: false };
|
|
17822
|
+
const stderr = result.stderr?.trim() ?? "";
|
|
17823
|
+
return { ok: false, message: `git merge-base failed: ${stderr || "non-zero exit"}` };
|
|
17824
|
+
}
|
|
17825
|
+
function commitCount(start, head, repoRoot) {
|
|
17826
|
+
if (!isFullCommit(start))
|
|
17827
|
+
return notACommit("start");
|
|
17828
|
+
if (!isFullCommit(head))
|
|
17829
|
+
return notACommit("head");
|
|
17830
|
+
const result = git(["rev-list", "--count", `${start}..${head}`], repoRoot, OBSERVE_GIT_TIMEOUT_MS);
|
|
17831
|
+
if (!result.ok)
|
|
17832
|
+
return result;
|
|
17833
|
+
const text = result.value.trim();
|
|
17834
|
+
if (!/^\d+$/.test(text))
|
|
17835
|
+
return { ok: false, message: "git gave no readable commit count" };
|
|
17836
|
+
return { ok: true, value: Number(text) };
|
|
17837
|
+
}
|
|
17838
|
+
function rangeNameStatus(start, head, repoRoot) {
|
|
17839
|
+
if (!isFullCommit(start))
|
|
17840
|
+
return notACommit("start");
|
|
17841
|
+
if (!isFullCommit(head))
|
|
17842
|
+
return notACommit("head");
|
|
17843
|
+
const result = git(["diff", "--name-status", "--no-renames", "-z", start, head], repoRoot, OBSERVE_GIT_TIMEOUT_MS);
|
|
17844
|
+
if (!result.ok)
|
|
17845
|
+
return result;
|
|
17846
|
+
return parseNameStatusZ(result.value);
|
|
17847
|
+
}
|
|
17848
|
+
function parseNameStatusZ(output) {
|
|
17849
|
+
if (output === "")
|
|
17850
|
+
return { ok: true, value: [] };
|
|
17851
|
+
if (!output.endsWith("\0"))
|
|
17852
|
+
return { ok: false, message: "git gave an unreadable list of changed files" };
|
|
17853
|
+
const fields = output.slice(0, -1).split("\0");
|
|
17854
|
+
if (fields.length % 2 !== 0)
|
|
17855
|
+
return { ok: false, message: "git gave an unreadable list of changed files" };
|
|
17856
|
+
const entries = [];
|
|
17857
|
+
for (let i = 0; i < fields.length; i += 2) {
|
|
17858
|
+
const status = fields[i];
|
|
17859
|
+
const path = fields[i + 1];
|
|
17860
|
+
if (!/^[A-Z][0-9]*$/.test(status) || status.startsWith("R") || status.startsWith("C") || path === "") {
|
|
17861
|
+
return { ok: false, message: "git gave an unreadable list of changed files" };
|
|
17862
|
+
}
|
|
17863
|
+
entries.push({ status: status.charAt(0), path });
|
|
17864
|
+
}
|
|
17865
|
+
return { ok: true, value: entries };
|
|
17866
|
+
}
|
|
17867
|
+
function readCodeLinkFacts(repoRoot, head) {
|
|
17868
|
+
const nothing = { repositoryAddress: null, headOnGitHub: false };
|
|
17869
|
+
if (!isFullCommit(head))
|
|
17870
|
+
return nothing;
|
|
17871
|
+
try {
|
|
17872
|
+
const remote = currentRemote(repoRoot);
|
|
17873
|
+
const url2 = git(["remote", "get-url", "--", remote], repoRoot, OBSERVE_GIT_TIMEOUT_MS);
|
|
17874
|
+
if (!url2.ok)
|
|
17875
|
+
return nothing;
|
|
17876
|
+
const repositoryAddress = githubRepositoryAddress(url2.value.trim());
|
|
17877
|
+
if (repositoryAddress === null)
|
|
17878
|
+
return nothing;
|
|
17879
|
+
const refs = git(["for-each-ref", "--contains", head, "--format=%(refname)", `refs/remotes/${remote}/`], repoRoot, OBSERVE_GIT_TIMEOUT_MS);
|
|
17880
|
+
if (!refs.ok)
|
|
17881
|
+
return nothing;
|
|
17882
|
+
const facts = { repositoryAddress, headOnGitHub: refs.value.trim() !== "" };
|
|
17883
|
+
return codeLinkFactsSchema.safeParse(facts).success ? facts : nothing;
|
|
17884
|
+
} catch {
|
|
17885
|
+
return nothing;
|
|
17886
|
+
}
|
|
17887
|
+
}
|
|
17888
|
+
function currentRemote(repoRoot) {
|
|
17889
|
+
const symbolic = git(["symbolic-ref", "--quiet", "HEAD"], repoRoot, OBSERVE_GIT_TIMEOUT_MS);
|
|
17890
|
+
if (!symbolic.ok)
|
|
17891
|
+
return "origin";
|
|
17892
|
+
const ref = symbolic.value.trim();
|
|
17893
|
+
if (!ref.startsWith("refs/heads/"))
|
|
17894
|
+
return "origin";
|
|
17895
|
+
const branch = ref.slice("refs/heads/".length);
|
|
17896
|
+
const configured = git(["config", "--get", `branch.${branch}.remote`], repoRoot, OBSERVE_GIT_TIMEOUT_MS);
|
|
17897
|
+
if (!configured.ok)
|
|
17898
|
+
return "origin";
|
|
17899
|
+
const remote = configured.value.trim();
|
|
17900
|
+
return remote === "" || remote === "." ? "origin" : remote;
|
|
17901
|
+
}
|
|
16831
17902
|
|
|
16832
17903
|
// dist/screen/secret-shape.js
|
|
16833
17904
|
function screenExcerpts(excerpts) {
|
|
@@ -18495,7 +19566,10 @@ var SESSION_NOTICE_SUFFIX = {
|
|
|
18495
19566
|
"no-repository": ".norepo",
|
|
18496
19567
|
"credential-rejected": ".credential",
|
|
18497
19568
|
"credential-renewed-for-this-run": ".renewed",
|
|
18498
|
-
"credential-missing": ".nocredential"
|
|
19569
|
+
"credential-missing": ".nocredential",
|
|
19570
|
+
"observe-no-start-here": ".observe-nostart",
|
|
19571
|
+
"observe-not-on-branch": ".observe-nobranch",
|
|
19572
|
+
"observe-no-start-recorded": ".observe-norecord"
|
|
18499
19573
|
};
|
|
18500
19574
|
function firstNoticeThisSession(notice, folder, sessionId) {
|
|
18501
19575
|
if (!sessionId)
|
|
@@ -18590,6 +19664,14 @@ function orphanSweep(repoRoot, refKnown, keepSessionId, now = Date.now()) {
|
|
|
18590
19664
|
}
|
|
18591
19665
|
}
|
|
18592
19666
|
}
|
|
19667
|
+
function observeThrottlePath(folder) {
|
|
19668
|
+
return join6(markerDir(), `${MARKER_PREFIX}${repoHash(folder)}.observe-throttle`);
|
|
19669
|
+
}
|
|
19670
|
+
function observeNoticesPath(folder, sessionId) {
|
|
19671
|
+
if (!sessionId)
|
|
19672
|
+
return null;
|
|
19673
|
+
return join6(markerDir(), `${MARKER_PREFIX}${repoHash(folder)}-${sessionId}.observe-said`);
|
|
19674
|
+
}
|
|
18593
19675
|
|
|
18594
19676
|
// dist/seat-configuration.js
|
|
18595
19677
|
import { existsSync as existsSync5, readFileSync as readFileSync8, realpathSync as realpathSync2 } from "node:fs";
|
|
@@ -18923,9 +20005,11 @@ function collectDiff(repoRoot, base) {
|
|
|
18923
20005
|
getDiff: (relPath) => getWorkingTreeFileDiff(relPath, repoRoot)
|
|
18924
20006
|
};
|
|
18925
20007
|
}
|
|
18926
|
-
async function runSessionDiff(input, kind, readSeat = readSeatConfiguration) {
|
|
20008
|
+
async function runSessionDiff(input, kind, readSeat = readSeatConfiguration, afterPass) {
|
|
18927
20009
|
const reply = hookReply(STOP_HOOK_EVENT[kind]);
|
|
18928
20010
|
await evaluateSession(input, kind, reply, readSeat);
|
|
20011
|
+
if (afterPass)
|
|
20012
|
+
await afterPass(reply);
|
|
18929
20013
|
reply.flush();
|
|
18930
20014
|
}
|
|
18931
20015
|
async function evaluateSession(input, kind, reply, readSeat) {
|
|
@@ -19106,17 +20190,328 @@ async function runSubagentStop() {
|
|
|
19106
20190
|
await runSessionDiff(input, "subagent-stop");
|
|
19107
20191
|
}
|
|
19108
20192
|
|
|
20193
|
+
// dist/observed.js
|
|
20194
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "node:fs";
|
|
20195
|
+
function emptyTally() {
|
|
20196
|
+
return { added: 0, changed: 0, removed: 0, testFilesAdded: 0 };
|
|
20197
|
+
}
|
|
20198
|
+
function kindOf(status) {
|
|
20199
|
+
if (status === "A")
|
|
20200
|
+
return "added";
|
|
20201
|
+
if (status === "D")
|
|
20202
|
+
return "removed";
|
|
20203
|
+
return "changed";
|
|
20204
|
+
}
|
|
20205
|
+
function countObservedChanges(entries, description) {
|
|
20206
|
+
const methodFiles = { added: 0, changed: 0, removed: 0 };
|
|
20207
|
+
const byPart = /* @__PURE__ */ new Map();
|
|
20208
|
+
const outside = emptyTally();
|
|
20209
|
+
const paths = [];
|
|
20210
|
+
const listed = /* @__PURE__ */ new Set();
|
|
20211
|
+
for (const entry of entries) {
|
|
20212
|
+
const kind = kindOf(entry.status);
|
|
20213
|
+
if (isMethodFile(entry.path)) {
|
|
20214
|
+
methodFiles[kind] += 1;
|
|
20215
|
+
continue;
|
|
20216
|
+
}
|
|
20217
|
+
const owner = ownerOf(entry.path, description);
|
|
20218
|
+
let tally;
|
|
20219
|
+
if (owner === null) {
|
|
20220
|
+
tally = outside;
|
|
20221
|
+
if (entry.path.length >= 1 && entry.path.length <= OBSERVED_PATH_MAX && paths.length < OBSERVED_PATHS_LISTED_MAX && !listed.has(entry.path)) {
|
|
20222
|
+
paths.push(entry.path);
|
|
20223
|
+
listed.add(entry.path);
|
|
20224
|
+
}
|
|
20225
|
+
} else {
|
|
20226
|
+
tally = byPart.get(owner) ?? emptyTally();
|
|
20227
|
+
byPart.set(owner, tally);
|
|
20228
|
+
}
|
|
20229
|
+
tally[kind] += 1;
|
|
20230
|
+
if (kind === "added" && isTestFile(entry.path))
|
|
20231
|
+
tally.testFilesAdded += 1;
|
|
20232
|
+
}
|
|
20233
|
+
const parts = description.parts.filter((part) => byPart.has(part.id)).map((part) => ({ part: part.id, ...byPart.get(part.id) }));
|
|
20234
|
+
return { parts, methodFiles, outsideEveryPart: { ...outside, paths } };
|
|
20235
|
+
}
|
|
20236
|
+
var OBSERVE_MIN_INTERVAL_MS = 6e4;
|
|
20237
|
+
var OBSERVE_RECHECK_MS = 6e5;
|
|
20238
|
+
var OBSERVE_REQUEST_DEADLINE_MS = 1500;
|
|
20239
|
+
async function requestJson(fetchFn, url2, init) {
|
|
20240
|
+
const controller = new AbortController();
|
|
20241
|
+
const timer = setTimeout(() => controller.abort(), OBSERVE_REQUEST_DEADLINE_MS);
|
|
20242
|
+
try {
|
|
20243
|
+
const response = await fetchFn(url2, { ...init, signal: controller.signal });
|
|
20244
|
+
let body = null;
|
|
20245
|
+
try {
|
|
20246
|
+
body = await response.json();
|
|
20247
|
+
} catch {
|
|
20248
|
+
body = null;
|
|
20249
|
+
}
|
|
20250
|
+
if (controller.signal.aborted)
|
|
20251
|
+
return { kind: "unreachable" };
|
|
20252
|
+
return { kind: "answer", status: response.status, body };
|
|
20253
|
+
} catch {
|
|
20254
|
+
return { kind: "unreachable" };
|
|
20255
|
+
} finally {
|
|
20256
|
+
clearTimeout(timer);
|
|
20257
|
+
}
|
|
20258
|
+
}
|
|
20259
|
+
function refusalReason(status) {
|
|
20260
|
+
return status >= 500 ? "unreachable" : "refused";
|
|
20261
|
+
}
|
|
20262
|
+
async function attemptObservation(opts) {
|
|
20263
|
+
const { repoRoot, head, config: config2, controlOrigin, fetchFn } = opts;
|
|
20264
|
+
const base = `${controlOrigin}/engagements/${encodeURIComponent(config2.guardEngagementId)}/observed`;
|
|
20265
|
+
const authorization = `Bearer ${config2.guardServiceToken}`;
|
|
20266
|
+
const basisAnswer = await requestJson(fetchFn, `${base}/basis`, {
|
|
20267
|
+
method: "GET",
|
|
20268
|
+
headers: { Authorization: authorization }
|
|
20269
|
+
});
|
|
20270
|
+
if (basisAnswer.kind === "unreachable")
|
|
20271
|
+
return { outcome: "not-sent", reason: "unreachable" };
|
|
20272
|
+
if (basisAnswer.status < 200 || basisAnswer.status >= 300) {
|
|
20273
|
+
return { outcome: "not-sent", reason: refusalReason(basisAnswer.status) };
|
|
20274
|
+
}
|
|
20275
|
+
const parsedBasis = observedBasisSchema.safeParse(basisAnswer.body);
|
|
20276
|
+
if (!parsedBasis.success)
|
|
20277
|
+
return { outcome: "not-sent", reason: "refused" };
|
|
20278
|
+
const basis = parsedBasis.data;
|
|
20279
|
+
if (basis.outcome === "nothing-to-count")
|
|
20280
|
+
return { outcome: "not-sent", reason: "nothing-to-count" };
|
|
20281
|
+
if (basis.outcome === "cannot-count")
|
|
20282
|
+
return { outcome: "not-sent", reason: "cannot-count", phase: basis.phase };
|
|
20283
|
+
if (repoRoot === null || !refExists(basis.startCommit, repoRoot)) {
|
|
20284
|
+
return { outcome: "not-sent", reason: "start-not-here" };
|
|
20285
|
+
}
|
|
20286
|
+
if (head === null)
|
|
20287
|
+
return { outcome: "not-sent", reason: "start-not-on-branch" };
|
|
20288
|
+
const ancestor = isAncestor(basis.startCommit, head, repoRoot);
|
|
20289
|
+
if (!ancestor.ok)
|
|
20290
|
+
return { outcome: "not-sent", reason: "unreachable" };
|
|
20291
|
+
if (!ancestor.value)
|
|
20292
|
+
return { outcome: "not-sent", reason: "start-not-on-branch" };
|
|
20293
|
+
const commits = commitCount(basis.startCommit, head, repoRoot);
|
|
20294
|
+
const committedAt = headCommittedAt(head, repoRoot);
|
|
20295
|
+
const entries = rangeNameStatus(basis.startCommit, head, repoRoot);
|
|
20296
|
+
if (!commits.ok || !committedAt.ok || !entries.ok)
|
|
20297
|
+
return { outcome: "not-sent", reason: "unreachable" };
|
|
20298
|
+
const report = {
|
|
20299
|
+
phase: basis.phase,
|
|
20300
|
+
startCommit: basis.startCommit,
|
|
20301
|
+
headCommit: head,
|
|
20302
|
+
headCommittedAt: committedAt.value,
|
|
20303
|
+
commits: commits.value,
|
|
20304
|
+
descriptionVersion: basis.descriptionVersion,
|
|
20305
|
+
...countObservedChanges(entries.value, basis.description),
|
|
20306
|
+
codeLink: readCodeLinkFacts(repoRoot, head)
|
|
20307
|
+
};
|
|
20308
|
+
if (!observedChangesReportSchema.safeParse(report).success)
|
|
20309
|
+
return { outcome: "not-sent", reason: "refused" };
|
|
20310
|
+
const sent = await requestJson(fetchFn, base, {
|
|
20311
|
+
method: "POST",
|
|
20312
|
+
headers: { Authorization: authorization, "Content-Type": "application/json" },
|
|
20313
|
+
body: JSON.stringify(report)
|
|
20314
|
+
});
|
|
20315
|
+
if (sent.kind === "unreachable")
|
|
20316
|
+
return { outcome: "not-sent", reason: "unreachable" };
|
|
20317
|
+
if (sent.status === 409)
|
|
20318
|
+
return { outcome: "not-sent", reason: "basis-moved" };
|
|
20319
|
+
if (sent.status < 200 || sent.status >= 300)
|
|
20320
|
+
return { outcome: "not-sent", reason: refusalReason(sent.status) };
|
|
20321
|
+
const answer = observedChangesReportResultSchema.safeParse(sent.body);
|
|
20322
|
+
if (!answer.success)
|
|
20323
|
+
return { outcome: "not-sent", reason: "refused" };
|
|
20324
|
+
return answer.data.outcome === "kept" ? { outcome: "kept" } : { outcome: "recorded", answer: answer.data };
|
|
20325
|
+
}
|
|
20326
|
+
function readThrottle(folder) {
|
|
20327
|
+
try {
|
|
20328
|
+
const parsed = JSON.parse(readFileSync9(observeThrottlePath(folder), "utf-8"));
|
|
20329
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
20330
|
+
return null;
|
|
20331
|
+
const { attemptedAt, accepted } = parsed;
|
|
20332
|
+
if (typeof attemptedAt !== "number")
|
|
20333
|
+
return null;
|
|
20334
|
+
const sent = accepted;
|
|
20335
|
+
const usable = sent !== null && sent !== void 0 && typeof sent.head === "string" && typeof sent.at === "number";
|
|
20336
|
+
return { attemptedAt, accepted: usable ? { head: sent.head, at: sent.at } : null };
|
|
20337
|
+
} catch {
|
|
20338
|
+
return null;
|
|
20339
|
+
}
|
|
20340
|
+
}
|
|
20341
|
+
function writeThrottle(folder, state) {
|
|
20342
|
+
try {
|
|
20343
|
+
writeFileSync4(observeThrottlePath(folder), JSON.stringify(state), "utf-8");
|
|
20344
|
+
} catch {
|
|
20345
|
+
}
|
|
20346
|
+
}
|
|
20347
|
+
function throttleAllows(state, head, now) {
|
|
20348
|
+
if (state === null)
|
|
20349
|
+
return true;
|
|
20350
|
+
if (now - state.attemptedAt < OBSERVE_MIN_INTERVAL_MS)
|
|
20351
|
+
return false;
|
|
20352
|
+
if (state.accepted === null)
|
|
20353
|
+
return true;
|
|
20354
|
+
if (head === null || head !== state.accepted.head)
|
|
20355
|
+
return true;
|
|
20356
|
+
return now - state.accepted.at >= OBSERVE_RECHECK_MS;
|
|
20357
|
+
}
|
|
20358
|
+
function locate(cwd) {
|
|
20359
|
+
const root = getRepoRoot(cwd);
|
|
20360
|
+
const repoRoot = root.ok ? root.value : null;
|
|
20361
|
+
const read = repoRoot === null ? null : headCommit(repoRoot);
|
|
20362
|
+
return { repoRoot, folder: repoRoot ?? cwd, head: read !== null && read.ok ? read.value : null };
|
|
20363
|
+
}
|
|
20364
|
+
function recordAttempt(folder, prior, head, at, attempt2) {
|
|
20365
|
+
const accepted = (attempt2.outcome === "recorded" || attempt2.outcome === "kept") && head !== null ? { head, at } : prior?.accepted ?? null;
|
|
20366
|
+
writeThrottle(folder, { attemptedAt: at, accepted });
|
|
20367
|
+
}
|
|
20368
|
+
async function runObserve(deps = {}) {
|
|
20369
|
+
let result;
|
|
20370
|
+
try {
|
|
20371
|
+
const configured = readConfig();
|
|
20372
|
+
if (!configured.config) {
|
|
20373
|
+
result = { outcome: "not-sent", reason: "not-configured" };
|
|
20374
|
+
} else {
|
|
20375
|
+
const now = deps.now ?? Date.now;
|
|
20376
|
+
const where = locate(deps.cwd ?? process.cwd());
|
|
20377
|
+
const prior = readThrottle(where.folder);
|
|
20378
|
+
const attempt2 = await attemptObservation({
|
|
20379
|
+
repoRoot: where.repoRoot,
|
|
20380
|
+
head: where.head,
|
|
20381
|
+
config: configured.config,
|
|
20382
|
+
controlOrigin: readControlOrigin().origin,
|
|
20383
|
+
fetchFn: deps.fetchFn ?? globalThis.fetch
|
|
20384
|
+
});
|
|
20385
|
+
recordAttempt(where.folder, prior, where.head, now(), attempt2);
|
|
20386
|
+
result = attempt2.outcome === "not-sent" ? { outcome: "not-sent", reason: attempt2.reason } : { outcome: attempt2.outcome };
|
|
20387
|
+
}
|
|
20388
|
+
} catch {
|
|
20389
|
+
result = { outcome: "not-sent", reason: "unreachable" };
|
|
20390
|
+
}
|
|
20391
|
+
return observeRunResultSchema.parse(result);
|
|
20392
|
+
}
|
|
20393
|
+
async function observeChanges(sessionId, reply, deps = {}) {
|
|
20394
|
+
try {
|
|
20395
|
+
const code = process.exitCode;
|
|
20396
|
+
if (code !== void 0 && code !== null && Number(code) !== 0)
|
|
20397
|
+
return;
|
|
20398
|
+
const configured = readConfig();
|
|
20399
|
+
if (!configured.config)
|
|
20400
|
+
return;
|
|
20401
|
+
const now = (deps.now ?? Date.now)();
|
|
20402
|
+
const where = locate(deps.cwd ?? process.cwd());
|
|
20403
|
+
const prior = readThrottle(where.folder);
|
|
20404
|
+
if (!throttleAllows(prior, where.head, now))
|
|
20405
|
+
return;
|
|
20406
|
+
writeThrottle(where.folder, { attemptedAt: now, accepted: prior?.accepted ?? null });
|
|
20407
|
+
const attempt2 = await attemptObservation({
|
|
20408
|
+
repoRoot: where.repoRoot,
|
|
20409
|
+
head: where.head,
|
|
20410
|
+
config: configured.config,
|
|
20411
|
+
controlOrigin: readControlOrigin().origin,
|
|
20412
|
+
fetchFn: deps.fetchFn ?? globalThis.fetch
|
|
20413
|
+
});
|
|
20414
|
+
recordAttempt(where.folder, prior, where.head, now, attempt2);
|
|
20415
|
+
for (const line2 of noticesFor(attempt2, where.folder, sessionId))
|
|
20416
|
+
reply.notice(line2);
|
|
20417
|
+
} catch {
|
|
20418
|
+
}
|
|
20419
|
+
}
|
|
20420
|
+
var R1_START_NOT_HERE = "Halfcycle cannot count what this phase has changed on this computer yet, because the commit the phase started from is not here. Get the project's latest history, and it counts again at the end of the next reply.";
|
|
20421
|
+
var R2_START_NOT_ON_BRANCH = "Halfcycle cannot count what this phase has changed on this branch, because the branch does not contain the commit the phase started from. Switch to the branch the phase is built on, and it counts again at the end of the next reply.";
|
|
20422
|
+
function r3NoStartRecorded(phase) {
|
|
20423
|
+
return `Halfcycle cannot count what phase "${phase}" changes, because no starting commit was recorded when it opened. To count from where the project is now, run: ${HALFCYCLE_COMMAND} open-phase ${quoteForShell(phase)} --actor "you" --evidence "<what shows this phase is ready to start>"`;
|
|
20424
|
+
}
|
|
20425
|
+
function r4OutsideThePlan(parts) {
|
|
20426
|
+
const first = parts.length === 1 ? "Halfcycle saw changes to a part this phase's plan does not include:" : "Halfcycle saw changes to parts this phase's plan does not include:";
|
|
20427
|
+
const lines = parts.map((entry) => {
|
|
20428
|
+
const said = entry.said.length === 0 ? NO_TASK_SAID_LINE : entry.said.map((task) => agentSaid(task)).join("; ");
|
|
20429
|
+
return `"${entry.name}": ${said}`;
|
|
20430
|
+
});
|
|
20431
|
+
return [first, ...lines, "The project's owner sees them again at the review."].join("\n");
|
|
20432
|
+
}
|
|
20433
|
+
function r5OutsideEveryPart(files, example) {
|
|
20434
|
+
if (files === 1) {
|
|
20435
|
+
return `Halfcycle saw 1 changed file that no part of the system owns: ${example}. If it belongs to a part, add it to that part in the description of the system.`;
|
|
20436
|
+
}
|
|
20437
|
+
return `Halfcycle saw ${files} changed files that no part of the system owns, such as ${example}. If they belong to a part, add them to that part in the description of the system.`;
|
|
20438
|
+
}
|
|
20439
|
+
function readSaid(path) {
|
|
20440
|
+
const empty = { parts: {}, outsideFiles: 0 };
|
|
20441
|
+
if (path === null)
|
|
20442
|
+
return empty;
|
|
20443
|
+
try {
|
|
20444
|
+
const parsed = JSON.parse(readFileSync9(path, "utf-8"));
|
|
20445
|
+
const parts = {};
|
|
20446
|
+
if (typeof parsed.parts === "object" && parsed.parts !== null) {
|
|
20447
|
+
for (const [part, reasons] of Object.entries(parsed.parts)) {
|
|
20448
|
+
if (Array.isArray(reasons))
|
|
20449
|
+
parts[part] = reasons.filter((r) => typeof r === "string");
|
|
20450
|
+
}
|
|
20451
|
+
}
|
|
20452
|
+
return { parts, outsideFiles: typeof parsed.outsideFiles === "number" ? parsed.outsideFiles : 0 };
|
|
20453
|
+
} catch {
|
|
20454
|
+
return empty;
|
|
20455
|
+
}
|
|
20456
|
+
}
|
|
20457
|
+
function writeSaid(path, state) {
|
|
20458
|
+
if (path === null)
|
|
20459
|
+
return;
|
|
20460
|
+
try {
|
|
20461
|
+
writeFileSync4(path, JSON.stringify(state), "utf-8");
|
|
20462
|
+
} catch {
|
|
20463
|
+
}
|
|
20464
|
+
}
|
|
20465
|
+
function reasonsOf(entry) {
|
|
20466
|
+
return entry.said.filter((task) => task.whyOutsidePlan !== void 0).map((task) => `${task.title}\0${task.whyOutsidePlan}`);
|
|
20467
|
+
}
|
|
20468
|
+
function noticesFor(attempt2, folder, sessionId) {
|
|
20469
|
+
if (attempt2.outcome === "not-sent") {
|
|
20470
|
+
if (attempt2.reason === "start-not-here") {
|
|
20471
|
+
return firstNoticeThisSession("observe-no-start-here", folder, sessionId) ? [R1_START_NOT_HERE] : [];
|
|
20472
|
+
}
|
|
20473
|
+
if (attempt2.reason === "start-not-on-branch") {
|
|
20474
|
+
return firstNoticeThisSession("observe-not-on-branch", folder, sessionId) ? [R2_START_NOT_ON_BRANCH] : [];
|
|
20475
|
+
}
|
|
20476
|
+
if (attempt2.reason === "cannot-count" && attempt2.phase !== void 0) {
|
|
20477
|
+
return firstNoticeThisSession("observe-no-start-recorded", folder, sessionId) ? [r3NoStartRecorded(attempt2.phase)] : [];
|
|
20478
|
+
}
|
|
20479
|
+
return [];
|
|
20480
|
+
}
|
|
20481
|
+
if (attempt2.outcome === "kept")
|
|
20482
|
+
return [];
|
|
20483
|
+
const { notInPlan, outside } = attempt2.answer;
|
|
20484
|
+
const path = observeNoticesPath(folder, sessionId);
|
|
20485
|
+
const prior = readSaid(path);
|
|
20486
|
+
const notices = [];
|
|
20487
|
+
const parts = {};
|
|
20488
|
+
let grew = false;
|
|
20489
|
+
for (const entry of notInPlan) {
|
|
20490
|
+
const reasons = reasonsOf(entry);
|
|
20491
|
+
parts[entry.part] = reasons;
|
|
20492
|
+
const before = prior.parts[entry.part];
|
|
20493
|
+
if (before === void 0 || reasons.some((reason) => !before.includes(reason)))
|
|
20494
|
+
grew = true;
|
|
20495
|
+
}
|
|
20496
|
+
if (grew)
|
|
20497
|
+
notices.push(r4OutsideThePlan(notInPlan));
|
|
20498
|
+
let outsideFiles = outside.files;
|
|
20499
|
+
if (outside.files > prior.outsideFiles) {
|
|
20500
|
+
if (outside.example !== null)
|
|
20501
|
+
notices.push(r5OutsideEveryPart(outside.files, outside.example));
|
|
20502
|
+
else
|
|
20503
|
+
outsideFiles = prior.outsideFiles;
|
|
20504
|
+
}
|
|
20505
|
+
writeSaid(path, { parts, outsideFiles });
|
|
20506
|
+
return notices;
|
|
20507
|
+
}
|
|
20508
|
+
|
|
19109
20509
|
// dist/hooks/stop.js
|
|
19110
|
-
async function runStop() {
|
|
20510
|
+
async function runStop(observeDeps = {}) {
|
|
19111
20511
|
const input = await readHookStdin();
|
|
19112
|
-
await runSessionDiff(input, "stop");
|
|
20512
|
+
await runSessionDiff(input, "stop", readSeatConfiguration, (reply) => observeChanges(input.session_id, reply, observeDeps));
|
|
19113
20513
|
}
|
|
19114
20514
|
|
|
19115
|
-
// dist/hooks/user-prompt-submit.js
|
|
19116
|
-
import { readFileSync as readFileSync9 } from "node:fs";
|
|
19117
|
-
import { homedir as homedir4 } from "node:os";
|
|
19118
|
-
import { join as join8 } from "node:path";
|
|
19119
|
-
|
|
19120
20515
|
// dist/hooks/scope-phrase.js
|
|
19121
20516
|
var APPROVE_PREFIX = /^approve /i;
|
|
19122
20517
|
var SEND_BACK_PREFIX = /^send back /i;
|
|
@@ -19150,6 +20545,128 @@ function matchScopePhraseShape(shape, shown) {
|
|
|
19150
20545
|
return { kind: "show-first", act: "send-back" };
|
|
19151
20546
|
}
|
|
19152
20547
|
|
|
20548
|
+
// dist/hooks/review-phrase.js
|
|
20549
|
+
var VERBS = [
|
|
20550
|
+
[/^accept /i, "accept"],
|
|
20551
|
+
[/^question /i, "question"],
|
|
20552
|
+
[/^send back /i, "send-back"],
|
|
20553
|
+
[/^carry /i, "carry"],
|
|
20554
|
+
[/^drop /i, "drop"]
|
|
20555
|
+
];
|
|
20556
|
+
function reviewPhraseShape(prompt) {
|
|
20557
|
+
if (typeof prompt !== "string")
|
|
20558
|
+
return null;
|
|
20559
|
+
const line2 = prompt.trim();
|
|
20560
|
+
if (LINE_BREAK.test(line2))
|
|
20561
|
+
return null;
|
|
20562
|
+
for (const [prefix, act] of VERBS) {
|
|
20563
|
+
const verb = prefix.exec(line2);
|
|
20564
|
+
if (verb === null)
|
|
20565
|
+
continue;
|
|
20566
|
+
const rest = line2.slice(verb[0].length);
|
|
20567
|
+
if (act === "accept") {
|
|
20568
|
+
if (!rest.includes("/"))
|
|
20569
|
+
return isValidPhaseIdentity(rest) ? { kind: "phase", reference: rest } : null;
|
|
20570
|
+
return partShape(act, rest, null);
|
|
20571
|
+
}
|
|
20572
|
+
const colon = rest.indexOf(":");
|
|
20573
|
+
if (colon === -1)
|
|
20574
|
+
return act === "send-back" ? partShape(act, rest, null) : null;
|
|
20575
|
+
return partShape(act, rest.slice(0, colon), rest.slice(colon + 1).trim());
|
|
20576
|
+
}
|
|
20577
|
+
return null;
|
|
20578
|
+
}
|
|
20579
|
+
function partShape(act, head, words) {
|
|
20580
|
+
const slash = head.indexOf("/");
|
|
20581
|
+
if (slash === -1)
|
|
20582
|
+
return null;
|
|
20583
|
+
const reference = head.slice(0, slash);
|
|
20584
|
+
const part = head.slice(slash + 1);
|
|
20585
|
+
if (!isValidPhaseIdentity(reference) || !SYSTEM_PART_ID_PATTERN.test(part))
|
|
20586
|
+
return null;
|
|
20587
|
+
return { kind: "part", act, reference, part, words };
|
|
20588
|
+
}
|
|
20589
|
+
function matchReviewPhraseShape(shape, reviews) {
|
|
20590
|
+
const entry = resolvePhaseReference(shape.reference, reviews);
|
|
20591
|
+
if (shape.kind === "phase") {
|
|
20592
|
+
if (entry === null)
|
|
20593
|
+
return { kind: "nothing" };
|
|
20594
|
+
return {
|
|
20595
|
+
kind: "accept",
|
|
20596
|
+
entry,
|
|
20597
|
+
reference: shape.reference,
|
|
20598
|
+
parts: entry.parts.filter((p) => p.built && p.decidable)
|
|
20599
|
+
};
|
|
20600
|
+
}
|
|
20601
|
+
if (entry === null)
|
|
20602
|
+
return { kind: "show-first", act: shape.act };
|
|
20603
|
+
const part = entry.parts.find((p) => p.part === shape.part);
|
|
20604
|
+
if (part === void 0)
|
|
20605
|
+
return { kind: "not-listed", act: shape.act, entry, part: shape.part };
|
|
20606
|
+
return { kind: "decide", act: shape.act, entry, reference: shape.reference, part, words: shape.words };
|
|
20607
|
+
}
|
|
20608
|
+
|
|
20609
|
+
// dist/hooks/shown-record.js
|
|
20610
|
+
import { chmodSync as chmodSync2, readFileSync as readFileSync10, renameSync as renameSync3, unlinkSync as unlinkSync4, writeFileSync as writeFileSync5 } from "node:fs";
|
|
20611
|
+
import { homedir as homedir4, platform as platform2 } from "node:os";
|
|
20612
|
+
import { join as join8 } from "node:path";
|
|
20613
|
+
function recordDir(engagementId, home) {
|
|
20614
|
+
if (!isEngagementId(engagementId))
|
|
20615
|
+
return null;
|
|
20616
|
+
return join8(home ?? homedir4(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId);
|
|
20617
|
+
}
|
|
20618
|
+
function readShownRecord(engagementId, home) {
|
|
20619
|
+
const dir = recordDir(engagementId, home);
|
|
20620
|
+
if (dir === null)
|
|
20621
|
+
return { entries: [], reviews: [] };
|
|
20622
|
+
try {
|
|
20623
|
+
return parseShownRecord(readFileSync10(join8(dir, SHOWN_PLANS_FILENAME), "utf-8"));
|
|
20624
|
+
} catch {
|
|
20625
|
+
return { entries: [], reviews: [] };
|
|
20626
|
+
}
|
|
20627
|
+
}
|
|
20628
|
+
function movedPart(part, act) {
|
|
20629
|
+
if (act === void 0)
|
|
20630
|
+
return part;
|
|
20631
|
+
return {
|
|
20632
|
+
...part,
|
|
20633
|
+
decidable: false,
|
|
20634
|
+
awaitingAnswer: part.awaitingAnswer || act === "question",
|
|
20635
|
+
sentBack: part.sentBack || act === "send-back"
|
|
20636
|
+
};
|
|
20637
|
+
}
|
|
20638
|
+
function moveListedReviewForward(engagementId, home, listed, reviewVersion, decided) {
|
|
20639
|
+
let temp = null;
|
|
20640
|
+
try {
|
|
20641
|
+
const dir = recordDir(engagementId, home);
|
|
20642
|
+
if (dir === null)
|
|
20643
|
+
return false;
|
|
20644
|
+
const path = join8(dir, SHOWN_PLANS_FILENAME);
|
|
20645
|
+
const current = parseShownRecord(readFileSync10(path, "utf-8"));
|
|
20646
|
+
const at = current.reviews.findIndex((r) => r.phase === listed.phase && r.shownAt === listed.shownAt && r.reviewVersion === listed.reviewVersion);
|
|
20647
|
+
if (at === -1)
|
|
20648
|
+
return false;
|
|
20649
|
+
const acts = new Map(decided.map((d) => [d.part, d.act]));
|
|
20650
|
+
const reviews = current.reviews.map((review, i) => i === at ? { ...review, reviewVersion, parts: review.parts.map((p) => movedPart(p, acts.get(p.part))) } : review);
|
|
20651
|
+
const body = serialiseShownRecord({ entries: current.entries, reviews });
|
|
20652
|
+
temp = join8(dir, `.${SHOWN_PLANS_FILENAME}.${process.pid}.tmp`);
|
|
20653
|
+
writeFileSync5(temp, body, { mode: 384 });
|
|
20654
|
+
if (platform2() !== "win32")
|
|
20655
|
+
chmodSync2(temp, 384);
|
|
20656
|
+
renameSync3(temp, path);
|
|
20657
|
+
return true;
|
|
20658
|
+
} catch {
|
|
20659
|
+
if (temp !== null) {
|
|
20660
|
+
try {
|
|
20661
|
+
unlinkSync4(temp);
|
|
20662
|
+
} catch {
|
|
20663
|
+
return false;
|
|
20664
|
+
}
|
|
20665
|
+
}
|
|
20666
|
+
return false;
|
|
20667
|
+
}
|
|
20668
|
+
}
|
|
20669
|
+
|
|
19153
20670
|
// dist/hooks/user-prompt-submit-lines.js
|
|
19154
20671
|
var TELL_THE_PERSON = "Tell the person.";
|
|
19155
20672
|
function line(text) {
|
|
@@ -19162,7 +20679,7 @@ function showCommand(phase) {
|
|
|
19162
20679
|
return `\`${HALFCYCLE_COMMAND} plan ${quoteForShell(phase)} --show\``;
|
|
19163
20680
|
}
|
|
19164
20681
|
function recordedLine(act, phase, planVersion) {
|
|
19165
|
-
return act === "approve" ? line(`Approved ${phase}, plan version ${planVersion}, in your session.`) : line(`Sent ${phase} back with your note, plan version ${planVersion}, in your session. The note is given first to the next session that asks what to do.`);
|
|
20682
|
+
return act === "approve" ? line(`Approved "${phase}", plan version ${planVersion}, in your session.`) : line(`Sent "${phase}" back with your note, plan version ${planVersion}, in your session. The note is given first to the next session that asks what to do.`);
|
|
19166
20683
|
}
|
|
19167
20684
|
function notAttendedLine(act) {
|
|
19168
20685
|
return line(`${nothingDone(act)}: approving or sending back by typing counts only when a person types it into an open session.`);
|
|
@@ -19177,7 +20694,7 @@ function showFirstLine(act) {
|
|
|
19177
20694
|
return line(`${nothingDone(act)}: that phase has not been listed here. List its parts first with \`${HALFCYCLE_COMMAND} plan <phase> --show\`, then type the line again.`);
|
|
19178
20695
|
}
|
|
19179
20696
|
function tooOldLine(act, phase) {
|
|
19180
|
-
return line(`${nothingDone(act)}: ${phase} was listed an hour or more ago. List it again with ${showCommand(phase)}, then type the line again within the hour.`);
|
|
20697
|
+
return line(`${nothingDone(act)}: "${phase}" was listed an hour or more ago. List it again with ${showCommand(phase)}, then type the line again within the hour.`);
|
|
19181
20698
|
}
|
|
19182
20699
|
function noteNeededLine() {
|
|
19183
20700
|
return line("Nothing was sent back: a send-back needs a note after the colon saying what should change.");
|
|
@@ -19204,11 +20721,11 @@ function lineForAnswer(act, phase, answer) {
|
|
|
19204
20721
|
case "show-first":
|
|
19205
20722
|
return showFirstLine(act);
|
|
19206
20723
|
case "plan-changed":
|
|
19207
|
-
return line(`${nothingDone(act)}: the plan for ${phase} changed after it was listed here. List it again with ${showCommand(phase)}, check the parts, then type the line again.`);
|
|
20724
|
+
return line(`${nothingDone(act)}: the plan for "${phase}" changed after it was listed here. List it again with ${showCommand(phase)}, check the parts, then type the line again.`);
|
|
19208
20725
|
case "already-approved":
|
|
19209
|
-
return answer.replanNeeded ? line(
|
|
20726
|
+
return answer.replanNeeded ? line(`"${phase}" is already approved, so nothing changed. Some parts wait because the description of the system changed since the plan was written: write the plan for "${phase}" again, run ${showCommand(phase)}, then approve the new version.`) : line(`"${phase}" is already approved, so nothing changed.`);
|
|
19210
20727
|
case "build-started":
|
|
19211
|
-
return line(`${nothingDone(act)}: tasks in ${phase} have started, so its scope can no longer be sent back. A built part goes back at the phase's review.`);
|
|
20728
|
+
return line(`${nothingDone(act)}: tasks in "${phase}" have started, so its scope can no longer be sent back. A built part goes back at the phase's review.`);
|
|
19212
20729
|
case "not-owner":
|
|
19213
20730
|
return notOwnerLine(act);
|
|
19214
20731
|
case "no-owner":
|
|
@@ -19225,14 +20742,215 @@ function lineForAnswer(act, phase, answer) {
|
|
|
19225
20742
|
}
|
|
19226
20743
|
}
|
|
19227
20744
|
}
|
|
20745
|
+
var NOTHING_RECORDED = "Nothing was recorded";
|
|
20746
|
+
var LIST_AGAIN = 'Say "review what was built" to list it again';
|
|
20747
|
+
function reviewNotAttendedLine() {
|
|
20748
|
+
return line(`${NOTHING_RECORDED}: reviewing by typing counts only when a person types it into an open session.`);
|
|
20749
|
+
}
|
|
20750
|
+
function reviewNotSignedInLine() {
|
|
20751
|
+
return line(`${NOTHING_RECORDED}: this project is not signed in on this computer. ${HOOK_REMEDY}`);
|
|
20752
|
+
}
|
|
20753
|
+
function reviewCannotReachLine() {
|
|
20754
|
+
return line(`${NOTHING_RECORDED}: Halfcycle could not be reached just now. Type the line again in a minute.`);
|
|
20755
|
+
}
|
|
20756
|
+
function reviewSwitchedOffLine() {
|
|
20757
|
+
return line(`${NOTHING_RECORDED}. ${TYPED_REVIEW_OFF_SENTENCE}`);
|
|
20758
|
+
}
|
|
20759
|
+
function reviewNotOwnerLine() {
|
|
20760
|
+
return line(`${NOTHING_RECORDED}: only the project's owner can review this phase.`);
|
|
20761
|
+
}
|
|
20762
|
+
function reviewNoOwnerLine() {
|
|
20763
|
+
return line(`${NOTHING_RECORDED}: this project has no owner, so nobody can review its phases.`);
|
|
20764
|
+
}
|
|
20765
|
+
function reviewWrongActLine(name, built) {
|
|
20766
|
+
return line(built ? `${NOTHING_RECORDED}: "${name}" was built, so it can be accepted, questioned or sent back.` : `${NOTHING_RECORDED}: "${name}" was not built, so it can only be carried or dropped.`);
|
|
20767
|
+
}
|
|
20768
|
+
function wordsActNoun(act) {
|
|
20769
|
+
switch (act) {
|
|
20770
|
+
case "question":
|
|
20771
|
+
return "a question";
|
|
20772
|
+
case "send-back":
|
|
20773
|
+
return "a send-back";
|
|
20774
|
+
case "carry":
|
|
20775
|
+
return "a carry";
|
|
20776
|
+
case "drop":
|
|
20777
|
+
return "a drop";
|
|
20778
|
+
default: {
|
|
20779
|
+
const unreachable = act;
|
|
20780
|
+
return unreachable;
|
|
20781
|
+
}
|
|
20782
|
+
}
|
|
20783
|
+
}
|
|
20784
|
+
function reviewNoWordsLine(act) {
|
|
20785
|
+
return line(`${NOTHING_RECORDED}: ${wordsActNoun(act)} needs words after the colon.`);
|
|
20786
|
+
}
|
|
20787
|
+
function reviewWordsTooLongLine() {
|
|
20788
|
+
return line(`${NOTHING_RECORDED}: the words after the colon are too long. Shorten them and type the line again.`);
|
|
20789
|
+
}
|
|
20790
|
+
function reviewWordsLookSecretLine() {
|
|
20791
|
+
return line(`${NOTHING_RECORDED}: the words look like they hold a password or key, so they never left this computer. Type the line again without it.`);
|
|
20792
|
+
}
|
|
20793
|
+
function reviewShowFirstLine() {
|
|
20794
|
+
return line(`${NOTHING_RECORDED}: that review has not been listed here. Say "review what was built" to list it, then type the line again.`);
|
|
20795
|
+
}
|
|
20796
|
+
function namedPhase(phase) {
|
|
20797
|
+
return `"${phase}"`;
|
|
20798
|
+
}
|
|
20799
|
+
function reviewPartNotListedLine(part, phase) {
|
|
20800
|
+
return line(`${NOTHING_RECORDED}: "${part}" is not a part of the review of ${namedPhase(phase)} as it was listed here.`);
|
|
20801
|
+
}
|
|
20802
|
+
function reviewNothingToAcceptLine(phase) {
|
|
20803
|
+
return line(`${NOTHING_RECORDED}: no built part of ${namedPhase(phase)} waits for your decision.`);
|
|
20804
|
+
}
|
|
20805
|
+
function reviewListedTooLongAgoLine(phase) {
|
|
20806
|
+
return line(`${NOTHING_RECORDED}: ${namedPhase(phase)} was listed an hour or more ago. ${LIST_AGAIN}, then type the line again within the hour.`);
|
|
20807
|
+
}
|
|
20808
|
+
function reviewChangedLine(phase) {
|
|
20809
|
+
return line(`${NOTHING_RECORDED}: the review of ${namedPhase(phase)} changed after it was listed here. ${LIST_AGAIN}, then type the line again.`);
|
|
20810
|
+
}
|
|
20811
|
+
function reviewAlreadyDecidedLine(name) {
|
|
20812
|
+
return line(`${NOTHING_RECORDED}: "${name}" is already decided in this review.`);
|
|
20813
|
+
}
|
|
20814
|
+
function reviewAwaitingAnswerLine(name) {
|
|
20815
|
+
return line(`${NOTHING_RECORDED}: "${name}" is waiting for the agent's answer to your question. Once it is in, say "review what was built" to list it again and decide the part.`);
|
|
20816
|
+
}
|
|
20817
|
+
function reviewSentBackLine(name) {
|
|
20818
|
+
return line(`${NOTHING_RECORDED}: "${name}" was sent back to building in this round, so it waits for the next round.`);
|
|
20819
|
+
}
|
|
20820
|
+
function reviewWalkChangedLine(phase, number4) {
|
|
20821
|
+
const typed = typedPhaseReference(phase, number4);
|
|
20822
|
+
return line(`${NOTHING_RECORDED}: phase ${namedPhase(phase)} cannot close, because its walk was recorded as clean and a defect is now recorded against it. Send the part with the defect back with send back ${typed}/<part>: <what is wrong>; when its tasks are finished, the close step opens the review again with the walk's defects.`);
|
|
20823
|
+
}
|
|
20824
|
+
function reviewNoReviewLine(phase) {
|
|
20825
|
+
return line(`${NOTHING_RECORDED}: phase ${namedPhase(phase)} has no review open.`);
|
|
20826
|
+
}
|
|
20827
|
+
function reviewCloseRefusedLine(phase, steps) {
|
|
20828
|
+
if (steps.length === 0) {
|
|
20829
|
+
return line(`${NOTHING_RECORDED}: phase ${namedPhase(phase)} could not close just now. Type the line again in a minute.`);
|
|
20830
|
+
}
|
|
20831
|
+
const labels = steps.map(unfinishedStepLabel).join(", ");
|
|
20832
|
+
const head = steps.length === 1 ? `${NOTHING_RECORDED}: phase ${namedPhase(phase)} cannot close while 1 of its steps is unfinished: ${labels}.` : `${NOTHING_RECORDED}: phase ${namedPhase(phase)} cannot close while ${steps.length} of its steps are unfinished: ${labels}.`;
|
|
20833
|
+
return line([head, ...steps.map(unfinishedStepLine), "Then type the line again."].join(" "));
|
|
20834
|
+
}
|
|
20835
|
+
function reviewRefusedLine(act) {
|
|
20836
|
+
return act === "accept" ? line(`${NOTHING_RECORDED}: Halfcycle did not accept the request. Update Halfcycle by running \`${HALFCYCLE_COMMAND}\` in this project's folder, then type the line again.`) : line(`${NOTHING_RECORDED}: the words were not accepted. Reword them in plain words and type the line again.`);
|
|
20837
|
+
}
|
|
20838
|
+
function stillWaitingOf(phase, waiting) {
|
|
20839
|
+
const named = namedPhase(phase);
|
|
20840
|
+
if (waiting === 0)
|
|
20841
|
+
return `No part of ${named} waits for your decision now.`;
|
|
20842
|
+
if (waiting === 1)
|
|
20843
|
+
return `1 part of ${named} still waits for your decision.`;
|
|
20844
|
+
return `${waiting} parts of ${named} still wait for your decision.`;
|
|
20845
|
+
}
|
|
20846
|
+
function stillWaiting(waiting) {
|
|
20847
|
+
if (waiting === 0)
|
|
20848
|
+
return "No part waits for your decision now.";
|
|
20849
|
+
if (waiting === 1)
|
|
20850
|
+
return "1 part still waits for your decision.";
|
|
20851
|
+
return `${waiting} parts still wait for your decision.`;
|
|
20852
|
+
}
|
|
20853
|
+
function decidedWords(decided) {
|
|
20854
|
+
switch (decided.act) {
|
|
20855
|
+
case "accept":
|
|
20856
|
+
return "accepted";
|
|
20857
|
+
case "question":
|
|
20858
|
+
return "questioned";
|
|
20859
|
+
case "send-back":
|
|
20860
|
+
return "sent back to building";
|
|
20861
|
+
case "carry":
|
|
20862
|
+
return decided.carriedTo === void 0 || decided.carriedTo === null ? "carried to the next phase planned" : `carried to phase "${decided.carriedTo.phase}"`;
|
|
20863
|
+
case "drop":
|
|
20864
|
+
return "dropped";
|
|
20865
|
+
default: {
|
|
20866
|
+
const unreachable = decided.act;
|
|
20867
|
+
return unreachable;
|
|
20868
|
+
}
|
|
20869
|
+
}
|
|
20870
|
+
}
|
|
20871
|
+
function reviewRecordedLine(decided, phase, waiting) {
|
|
20872
|
+
return line(`Recorded in your session: "${decided.name}" ${decidedWords(decided)}. ${stillWaitingOf(phase, waiting)}`);
|
|
20873
|
+
}
|
|
20874
|
+
function reviewBulkRecordedLine(accepted, phase, waiting) {
|
|
20875
|
+
const named = namedPhase(phase);
|
|
20876
|
+
const first = accepted === 1 ? `Recorded in your session: 1 part of ${named} accepted.` : `Recorded in your session: ${accepted} parts of ${named} accepted.`;
|
|
20877
|
+
return line(`${first} ${stillWaiting(waiting)}`);
|
|
20878
|
+
}
|
|
20879
|
+
function reviewClosedLine(phase) {
|
|
20880
|
+
return `Recorded in your session. That was the last decision: phase ${namedPhase(phase)} is closed. Run its close step now, then tell the person what it says.`;
|
|
20881
|
+
}
|
|
20882
|
+
function reviewNoPartsClosedLine(phase) {
|
|
20883
|
+
return `Recorded in your session. Phase ${namedPhase(phase)} had no parts to review, so it is closed. Run its close step now, then tell the person what it says.`;
|
|
20884
|
+
}
|
|
20885
|
+
function notDecidableLine(answer) {
|
|
20886
|
+
const first = answer.parts[0];
|
|
20887
|
+
switch (first.why) {
|
|
20888
|
+
case "question-waiting":
|
|
20889
|
+
return reviewAwaitingAnswerLine(first.name);
|
|
20890
|
+
case "sent-back":
|
|
20891
|
+
return reviewSentBackLine(first.name);
|
|
20892
|
+
case "settled":
|
|
20893
|
+
return reviewAlreadyDecidedLine(first.name);
|
|
20894
|
+
default: {
|
|
20895
|
+
const unreachable = first.why;
|
|
20896
|
+
return unreachable;
|
|
20897
|
+
}
|
|
20898
|
+
}
|
|
20899
|
+
}
|
|
20900
|
+
function reviewLineForAnswer(context, answer) {
|
|
20901
|
+
const { phase } = context;
|
|
20902
|
+
switch (answer.outcome) {
|
|
20903
|
+
case "recorded": {
|
|
20904
|
+
if (answer.closed) {
|
|
20905
|
+
return answer.decided.length === 0 ? reviewNoPartsClosedLine(phase) : reviewClosedLine(phase);
|
|
20906
|
+
}
|
|
20907
|
+
if (context.bulk)
|
|
20908
|
+
return reviewBulkRecordedLine(answer.decided.length, phase, answer.waiting);
|
|
20909
|
+
return reviewRecordedLine(answer.decided[0], phase, answer.waiting);
|
|
20910
|
+
}
|
|
20911
|
+
case "review-changed":
|
|
20912
|
+
return reviewChangedLine(phase);
|
|
20913
|
+
case "no-review":
|
|
20914
|
+
return reviewNoReviewLine(phase);
|
|
20915
|
+
case "not-decidable":
|
|
20916
|
+
return notDecidableLine(answer);
|
|
20917
|
+
case "close-refused":
|
|
20918
|
+
return reviewCloseRefusedLine(phase, answer.steps);
|
|
20919
|
+
case "walk-changed":
|
|
20920
|
+
return reviewWalkChangedLine(phase, context.number);
|
|
20921
|
+
case "not-owner":
|
|
20922
|
+
return reviewNotOwnerLine();
|
|
20923
|
+
case "no-owner":
|
|
20924
|
+
return reviewNoOwnerLine();
|
|
20925
|
+
case "refused":
|
|
20926
|
+
return reviewRefusedLine(context.act);
|
|
20927
|
+
case "unavailable":
|
|
20928
|
+
return reviewCannotReachLine();
|
|
20929
|
+
case "show-first":
|
|
20930
|
+
return reviewShowFirstLine();
|
|
20931
|
+
case "not-offered":
|
|
20932
|
+
return reviewSwitchedOffLine();
|
|
20933
|
+
default: {
|
|
20934
|
+
const unreachable = answer;
|
|
20935
|
+
return unreachable;
|
|
20936
|
+
}
|
|
20937
|
+
}
|
|
20938
|
+
}
|
|
19228
20939
|
|
|
19229
20940
|
// dist/hooks/user-prompt-submit.js
|
|
19230
20941
|
var CHAT_REQUEST_TIMEOUT_MS = 6e3;
|
|
19231
20942
|
var SHOWN_ENTRY_MAX_AGE_MS = 60 * 60 * 1e3;
|
|
19232
20943
|
async function runUserPromptSubmit(opts) {
|
|
19233
20944
|
let act = null;
|
|
20945
|
+
const review = { recognised: false };
|
|
19234
20946
|
try {
|
|
19235
20947
|
const prompt = promptOf(opts.input);
|
|
20948
|
+
const reviewShape = reviewPhraseShape(prompt);
|
|
20949
|
+
if (reviewShape !== null) {
|
|
20950
|
+
const out = await decideReview(reviewShape, opts, review);
|
|
20951
|
+
return out === "" ? "" : `${out}
|
|
20952
|
+
`;
|
|
20953
|
+
}
|
|
19236
20954
|
const shape = scopePhraseShape(prompt);
|
|
19237
20955
|
if (shape === null)
|
|
19238
20956
|
return "";
|
|
@@ -19240,10 +20958,114 @@ async function runUserPromptSubmit(opts) {
|
|
|
19240
20958
|
return `${await decide(shape, opts)}
|
|
19241
20959
|
`;
|
|
19242
20960
|
} catch {
|
|
20961
|
+
if (review.recognised)
|
|
20962
|
+
return `${reviewCannotReachLine()}
|
|
20963
|
+
`;
|
|
19243
20964
|
return act === null ? "" : `${cannotReachLine(act)}
|
|
19244
20965
|
`;
|
|
19245
20966
|
}
|
|
19246
20967
|
}
|
|
20968
|
+
async function decideReview(shape, opts, progress) {
|
|
20969
|
+
const env = opts.env ?? process.env;
|
|
20970
|
+
if (shape.kind === "phase") {
|
|
20971
|
+
const read2 = readConfig(env);
|
|
20972
|
+
if (read2.config === null)
|
|
20973
|
+
return "";
|
|
20974
|
+
const match2 = matchReviewPhraseShape(shape, readShownRecord(read2.config.guardEngagementId, opts.home).reviews);
|
|
20975
|
+
if (match2.kind === "nothing")
|
|
20976
|
+
return "";
|
|
20977
|
+
progress.recognised = true;
|
|
20978
|
+
if (!readSessionAttended(env))
|
|
20979
|
+
return reviewNotAttendedLine();
|
|
20980
|
+
return checkAndSendReview(match2, read2.config.guardEngagementId, read2.config.guardServiceToken, env, opts);
|
|
20981
|
+
}
|
|
20982
|
+
progress.recognised = true;
|
|
20983
|
+
if (!readSessionAttended(env))
|
|
20984
|
+
return reviewNotAttendedLine();
|
|
20985
|
+
const read = readConfig(env);
|
|
20986
|
+
if (read.config === null) {
|
|
20987
|
+
return readNeverConfiguredReason(env) === "not-this-account" ? reviewNotOwnerLine() : reviewNotSignedInLine();
|
|
20988
|
+
}
|
|
20989
|
+
const match = matchReviewPhraseShape(shape, readShownRecord(read.config.guardEngagementId, opts.home).reviews);
|
|
20990
|
+
return checkAndSendReview(match, read.config.guardEngagementId, read.config.guardServiceToken, env, opts);
|
|
20991
|
+
}
|
|
20992
|
+
async function checkAndSendReview(match, engagementId, credential, env, opts) {
|
|
20993
|
+
if (match.kind === "nothing" || match.kind === "show-first")
|
|
20994
|
+
return reviewShowFirstLine();
|
|
20995
|
+
if (match.kind === "not-listed")
|
|
20996
|
+
return reviewPartNotListedLine(match.part, match.entry.phase);
|
|
20997
|
+
const { entry } = match;
|
|
20998
|
+
if (!listedRecently(entry.shownAt, (opts.now ?? Date.now)()))
|
|
20999
|
+
return reviewListedTooLongAgoLine(entry.phase);
|
|
21000
|
+
let candidate;
|
|
21001
|
+
if (match.kind === "accept") {
|
|
21002
|
+
if (entry.parts.length > 0 && match.parts.length === 0)
|
|
21003
|
+
return reviewNothingToAcceptLine(entry.phase);
|
|
21004
|
+
candidate = {
|
|
21005
|
+
act: "accept",
|
|
21006
|
+
reference: match.reference,
|
|
21007
|
+
reviewVersion: entry.reviewVersion,
|
|
21008
|
+
parts: match.parts.map((p) => p.part)
|
|
21009
|
+
};
|
|
21010
|
+
} else {
|
|
21011
|
+
const { act, part } = match;
|
|
21012
|
+
if (!reviewActsFor(part.built).includes(act))
|
|
21013
|
+
return reviewWrongActLine(part.name, part.built);
|
|
21014
|
+
let words = "";
|
|
21015
|
+
if (act !== "accept") {
|
|
21016
|
+
words = match.words ?? "";
|
|
21017
|
+
if (words === "")
|
|
21018
|
+
return reviewNoWordsLine(act);
|
|
21019
|
+
if (!reviewNoteWireSchema.safeParse(words).success)
|
|
21020
|
+
return reviewWordsTooLongLine();
|
|
21021
|
+
if (looksLikeSecret(words))
|
|
21022
|
+
return reviewWordsLookSecretLine();
|
|
21023
|
+
}
|
|
21024
|
+
if (part.awaitingAnswer)
|
|
21025
|
+
return reviewAwaitingAnswerLine(part.name);
|
|
21026
|
+
if (part.sentBack)
|
|
21027
|
+
return reviewSentBackLine(part.name);
|
|
21028
|
+
if (!part.decidable)
|
|
21029
|
+
return reviewAlreadyDecidedLine(part.name);
|
|
21030
|
+
candidate = act === "accept" ? { act, reference: match.reference, reviewVersion: entry.reviewVersion, parts: [part.part] } : { act, reference: match.reference, reviewVersion: entry.reviewVersion, part: part.part, note: words };
|
|
21031
|
+
}
|
|
21032
|
+
const request = chatReviewRequestSchema.safeParse(candidate);
|
|
21033
|
+
if (!request.success)
|
|
21034
|
+
return reviewShowFirstLine();
|
|
21035
|
+
return postReview(match.kind === "accept", entry, request.data, engagementId, credential, env, opts);
|
|
21036
|
+
}
|
|
21037
|
+
async function postReview(bulk, entry, request, engagementId, credential, env, opts) {
|
|
21038
|
+
const fetchFn = opts.fetchFn ?? globalThis.fetch;
|
|
21039
|
+
const url2 = `${readControlOrigin(env).origin}/engagements/${encodeURIComponent(engagementId)}/phases/${encodeURIComponent(entry.phase)}/review/chat`;
|
|
21040
|
+
const controller = new AbortController();
|
|
21041
|
+
const timer = setTimeout(() => controller.abort(), CHAT_REQUEST_TIMEOUT_MS);
|
|
21042
|
+
try {
|
|
21043
|
+
const response = await fetchFn(url2, {
|
|
21044
|
+
method: "POST",
|
|
21045
|
+
headers: { Authorization: `Bearer ${credential}`, "Content-Type": "application/json" },
|
|
21046
|
+
body: JSON.stringify(request),
|
|
21047
|
+
signal: controller.signal
|
|
21048
|
+
});
|
|
21049
|
+
if (response.status === 401)
|
|
21050
|
+
return reviewNotSignedInLine();
|
|
21051
|
+
if (response.status === 429 || response.status >= 500)
|
|
21052
|
+
return reviewCannotReachLine();
|
|
21053
|
+
const parsed = chatReviewResultSchema.safeParse(await response.json());
|
|
21054
|
+
if (!parsed.success)
|
|
21055
|
+
return reviewCannotReachLine();
|
|
21056
|
+
const answer = parsed.data;
|
|
21057
|
+
if (answer.outcome === "recorded") {
|
|
21058
|
+
if (response.status !== 200)
|
|
21059
|
+
return reviewCannotReachLine();
|
|
21060
|
+
moveListedReviewForward(engagementId, opts.home, entry, answer.reviewVersion, answer.decided);
|
|
21061
|
+
}
|
|
21062
|
+
return reviewLineForAnswer({ act: request.act, bulk, phase: entry.phase, number: entry.number }, answer);
|
|
21063
|
+
} catch {
|
|
21064
|
+
return reviewCannotReachLine();
|
|
21065
|
+
} finally {
|
|
21066
|
+
clearTimeout(timer);
|
|
21067
|
+
}
|
|
21068
|
+
}
|
|
19247
21069
|
async function decide(shape, opts) {
|
|
19248
21070
|
const { act } = shape;
|
|
19249
21071
|
const env = opts.env ?? process.env;
|
|
@@ -19254,12 +21076,12 @@ async function decide(shape, opts) {
|
|
|
19254
21076
|
return readNeverConfiguredReason(env) === "not-this-account" ? notOwnerLine(act) : notSignedInLine(act);
|
|
19255
21077
|
}
|
|
19256
21078
|
const { guardServiceToken, guardEngagementId } = read.config;
|
|
19257
|
-
const shown =
|
|
21079
|
+
const shown = readShownRecord(guardEngagementId, opts.home).entries;
|
|
19258
21080
|
const match = matchScopePhraseShape(shape, shown);
|
|
19259
21081
|
if (match.kind === "show-first")
|
|
19260
21082
|
return showFirstLine(act);
|
|
19261
21083
|
const { entry } = match;
|
|
19262
|
-
if (!
|
|
21084
|
+
if (!listedRecently(entry.shownAt, (opts.now ?? Date.now)()))
|
|
19263
21085
|
return tooOldLine(act, entry.phase);
|
|
19264
21086
|
let candidate;
|
|
19265
21087
|
if (match.kind === "send-back") {
|
|
@@ -19288,19 +21110,8 @@ function promptOf(input) {
|
|
|
19288
21110
|
return void 0;
|
|
19289
21111
|
}
|
|
19290
21112
|
}
|
|
19291
|
-
function
|
|
19292
|
-
|
|
19293
|
-
return [];
|
|
19294
|
-
let text;
|
|
19295
|
-
try {
|
|
19296
|
-
text = readFileSync9(join8(home ?? homedir4(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, SHOWN_PLANS_FILENAME), "utf-8");
|
|
19297
|
-
} catch {
|
|
19298
|
-
return [];
|
|
19299
|
-
}
|
|
19300
|
-
return parseShownPlans(text);
|
|
19301
|
-
}
|
|
19302
|
-
function shownRecently(entry, now) {
|
|
19303
|
-
const age = now - Date.parse(entry.shownAt);
|
|
21113
|
+
function listedRecently(shownAt, now) {
|
|
21114
|
+
const age = now - Date.parse(shownAt);
|
|
19304
21115
|
return Number.isFinite(age) && age >= 0 && age < SHOWN_ENTRY_MAX_AGE_MS;
|
|
19305
21116
|
}
|
|
19306
21117
|
async function post(act, entry, request, engagementId, credential, env, fetchFn = globalThis.fetch) {
|
|
@@ -19332,7 +21143,7 @@ async function post(act, entry, request, engagementId, credential, env, fetchFn
|
|
|
19332
21143
|
}
|
|
19333
21144
|
|
|
19334
21145
|
// dist/resolve-engagement.js
|
|
19335
|
-
import { existsSync as existsSync6, readFileSync as
|
|
21146
|
+
import { existsSync as existsSync6, readFileSync as readFileSync11, realpathSync as realpathSync3 } from "node:fs";
|
|
19336
21147
|
import { join as join9, sep as sep2 } from "node:path";
|
|
19337
21148
|
import { homedir as homedir5 } from "node:os";
|
|
19338
21149
|
function defaultRegistryPath() {
|
|
@@ -19342,7 +21153,7 @@ function readRegistry(registryPath = defaultRegistryPath()) {
|
|
|
19342
21153
|
if (!existsSync6(registryPath))
|
|
19343
21154
|
return {};
|
|
19344
21155
|
try {
|
|
19345
|
-
const parsed = JSON.parse(
|
|
21156
|
+
const parsed = JSON.parse(readFileSync11(registryPath, "utf-8"));
|
|
19346
21157
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
19347
21158
|
return {};
|
|
19348
21159
|
const out = {};
|
|
@@ -19390,11 +21201,11 @@ function runResolveEngagement(cwd, registryPath) {
|
|
|
19390
21201
|
}
|
|
19391
21202
|
|
|
19392
21203
|
// dist/refresh-credential.js
|
|
19393
|
-
import { readFileSync as
|
|
21204
|
+
import { readFileSync as readFileSync13 } from "node:fs";
|
|
19394
21205
|
import { resolve as resolve4 } from "node:path";
|
|
19395
21206
|
|
|
19396
21207
|
// dist/mcp-headers.js
|
|
19397
|
-
import { readFileSync as
|
|
21208
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
19398
21209
|
import { homedir as homedir6 } from "node:os";
|
|
19399
21210
|
import { dirname as dirname4, resolve as resolve3 } from "node:path";
|
|
19400
21211
|
function defaultWriteErr(text) {
|
|
@@ -19414,7 +21225,7 @@ function projectRootFromBinary(binaryPath) {
|
|
|
19414
21225
|
function readPinnedVersion(root) {
|
|
19415
21226
|
let raw;
|
|
19416
21227
|
try {
|
|
19417
|
-
raw =
|
|
21228
|
+
raw = readFileSync12(resolve3(root, BUNDLE_PIN_REL), "utf-8");
|
|
19418
21229
|
} catch {
|
|
19419
21230
|
return void 0;
|
|
19420
21231
|
}
|
|
@@ -19640,7 +21451,7 @@ var INITIALIZE_BODY = JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initializ
|
|
|
19640
21451
|
function registeredMcpUrl(projectRoot) {
|
|
19641
21452
|
let parsed;
|
|
19642
21453
|
try {
|
|
19643
|
-
parsed = JSON.parse(
|
|
21454
|
+
parsed = JSON.parse(readFileSync13(resolve4(projectRoot, MCP_REGISTRATION_REL), "utf-8"));
|
|
19644
21455
|
} catch {
|
|
19645
21456
|
return void 0;
|
|
19646
21457
|
}
|
|
@@ -19747,7 +21558,7 @@ function positionRequestBody(engagementId) {
|
|
|
19747
21558
|
params: { name: "halfcycle_position", arguments: { engagementId } }
|
|
19748
21559
|
});
|
|
19749
21560
|
}
|
|
19750
|
-
async function
|
|
21561
|
+
async function fetchPositionNotices(url2, bearer, engagementId, fetchFn, deadlineMs) {
|
|
19751
21562
|
const controller = new AbortController();
|
|
19752
21563
|
const timer = setTimeout(() => controller.abort(), deadlineMs);
|
|
19753
21564
|
try {
|
|
@@ -19794,15 +21605,17 @@ async function fetchScopeNotices(url2, bearer, engagementId, fetchFn, deadlineMs
|
|
|
19794
21605
|
} catch {
|
|
19795
21606
|
return void 0;
|
|
19796
21607
|
}
|
|
19797
|
-
|
|
19798
|
-
|
|
19799
|
-
|
|
21608
|
+
if (typeof body !== "object" || body === null)
|
|
21609
|
+
return void 0;
|
|
21610
|
+
const scopeParsed = scopeNoticesSchema.safeParse(body["scope"]);
|
|
21611
|
+
const reviewParsed = reviewNoticesSchema.safeParse(body["review"]);
|
|
21612
|
+
return { scope: scopeParsed.success ? scopeParsed.data : void 0, review: reviewParsed.success ? reviewParsed.data : void 0 };
|
|
19800
21613
|
} finally {
|
|
19801
21614
|
clearTimeout(timer);
|
|
19802
21615
|
}
|
|
19803
21616
|
}
|
|
19804
|
-
function scopeNoticeLines(scope) {
|
|
19805
|
-
const text = scopeNoticeText(scope, { offerTyping: scope.typedApprovalOffered ? "yes" : "off" });
|
|
21617
|
+
function scopeNoticeLines(scope, engagementId) {
|
|
21618
|
+
const text = scopeNoticeText(scope, { engagementId, offerTyping: scope.typedApprovalOffered ? "yes" : "off" });
|
|
19806
21619
|
return [...text.sentBack, ...text.waiting];
|
|
19807
21620
|
}
|
|
19808
21621
|
function defaultWriteErr2(text) {
|
|
@@ -19833,9 +21646,11 @@ async function runRefreshCredential(opts) {
|
|
|
19833
21646
|
const remainingForMargin = SESSION_START_HOOK_TIMEOUT_MS - SESSION_START_MARGIN_MS - (Date.now() - startedAt);
|
|
19834
21647
|
const positionDeadline = Math.min(opts.scopeNoticeDeadlineMs ?? SCOPE_NOTICE_DEADLINE_MS, remainingForMargin);
|
|
19835
21648
|
if (positionDeadline > 0) {
|
|
19836
|
-
const
|
|
19837
|
-
if (scope !== void 0)
|
|
19838
|
-
parts.push(...scopeNoticeLines(scope));
|
|
21649
|
+
const notices = await fetchPositionNotices(connection.url, connection.bearer, engagementId, opts.fetchFn ?? globalThis.fetch, positionDeadline);
|
|
21650
|
+
if (notices?.scope !== void 0)
|
|
21651
|
+
parts.push(...scopeNoticeLines(notices.scope, engagementId));
|
|
21652
|
+
if (notices?.review !== void 0)
|
|
21653
|
+
parts.push(...reviewNoticeText(notices.review));
|
|
19839
21654
|
}
|
|
19840
21655
|
}
|
|
19841
21656
|
} else if (connection.sentence === CANNOT_REACH_SENTENCE || connection.sentence === SERVICE_PROBLEM_SENTENCE) {
|
|
@@ -19869,7 +21684,7 @@ async function runRefreshCredential(opts) {
|
|
|
19869
21684
|
import { fileURLToPath } from "node:url";
|
|
19870
21685
|
|
|
19871
21686
|
// dist/capture.js
|
|
19872
|
-
import { readFileSync as
|
|
21687
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync6, mkdirSync as mkdirSync3, existsSync as existsSync7 } from "node:fs";
|
|
19873
21688
|
import { dirname as dirname5 } from "node:path";
|
|
19874
21689
|
function runCapture(provider, endpoint, opts = {}) {
|
|
19875
21690
|
const repoRoot = opts.repoRoot ?? process.cwd();
|
|
@@ -19906,7 +21721,7 @@ function runCapture(provider, endpoint, opts = {}) {
|
|
|
19906
21721
|
const manifestFile = manifestPath(repoRoot);
|
|
19907
21722
|
try {
|
|
19908
21723
|
mkdirSync3(dirname5(fixture), { recursive: true });
|
|
19909
|
-
|
|
21724
|
+
writeFileSync6(fixture, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
19910
21725
|
} catch (err) {
|
|
19911
21726
|
const msg = err instanceof Error ? err.message : String(err);
|
|
19912
21727
|
return { ok: false, kind: "write-failed", message: `could not write fixture: ${msg}` };
|
|
@@ -19937,7 +21752,7 @@ function readInputFile(fromFile) {
|
|
|
19937
21752
|
if (!fromFile)
|
|
19938
21753
|
return void 0;
|
|
19939
21754
|
try {
|
|
19940
|
-
return
|
|
21755
|
+
return readFileSync14(fromFile, "utf-8");
|
|
19941
21756
|
} catch {
|
|
19942
21757
|
return void 0;
|
|
19943
21758
|
}
|
|
@@ -19961,6 +21776,17 @@ async function main() {
|
|
|
19961
21776
|
process.stdout.write(reply, () => process.exit(0));
|
|
19962
21777
|
return;
|
|
19963
21778
|
}
|
|
21779
|
+
if (cmd === "observe") {
|
|
21780
|
+
let line2;
|
|
21781
|
+
try {
|
|
21782
|
+
line2 = JSON.stringify(await runObserve());
|
|
21783
|
+
} catch {
|
|
21784
|
+
line2 = JSON.stringify({ outcome: "not-sent", reason: "unreachable" });
|
|
21785
|
+
}
|
|
21786
|
+
process.stdout.write(`${line2}
|
|
21787
|
+
`, () => process.exit(0));
|
|
21788
|
+
return;
|
|
21789
|
+
}
|
|
19964
21790
|
if (cmd === "mcp-headers") {
|
|
19965
21791
|
const line2 = await mcpHeaders({ binaryPath: fileURLToPath(import.meta.url) });
|
|
19966
21792
|
process.stdout.write(line2, () => process.exit(0));
|
|
@@ -20043,6 +21869,7 @@ Commit the fixture; the next guard evaluation of this endpoint will pass.
|
|
|
20043
21869
|
halfcycle-runner hook user-prompt-submit
|
|
20044
21870
|
halfcycle-runner refresh-credential
|
|
20045
21871
|
halfcycle-runner mcp-headers
|
|
21872
|
+
halfcycle-runner observe
|
|
20046
21873
|
halfcycle-runner resolve-engagement <cwd>
|
|
20047
21874
|
halfcycle-runner capture <provider> <endpoint> [--from <file>]
|
|
20048
21875
|
`);
|