halfcycle 0.4.8 → 0.4.9
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 +2031 -216
- 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/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 +3189 -1401
- package/dist/cli.js.map +3 -3
- package/dist/close-phase.d.ts +215 -134
- package/dist/close-phase.d.ts.map +1 -1
- package/dist/draw/index.d.ts +25 -5
- 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 +1624 -204
- 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 +59 -18
- package/dist/plan.d.ts.map +1 -1
- package/dist/pre-create-checks.d.ts.map +1 -1
- package/dist/review.d.ts +228 -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 +1 -1
- 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,6 +15950,174 @@ var systemDrawRefusedSchema = external_exports.object({
|
|
|
15852
15950
|
var systemDrawUnavailableSchema = external_exports.object({
|
|
15853
15951
|
outcome: external_exports.literal("unavailable")
|
|
15854
15952
|
}).strict();
|
|
15953
|
+
var systemDrawNotClosedSchema = external_exports.object({
|
|
15954
|
+
outcome: external_exports.literal("not-closed")
|
|
15955
|
+
}).strict();
|
|
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();
|
|
15855
16121
|
|
|
15856
16122
|
// ../events/dist/phase-plan.js
|
|
15857
16123
|
var PHASE_PLAN_FORMAT = 1;
|
|
@@ -15904,6 +16170,13 @@ var scopeDecisionPartSchema = external_exports.object({
|
|
|
15904
16170
|
note: scopeNoteWireSchema.optional()
|
|
15905
16171
|
}).strict();
|
|
15906
16172
|
var planVersionSchema = external_exports.number().int().min(1);
|
|
16173
|
+
var carriedPhaseSchema = external_exports.object({
|
|
16174
|
+
phase: scopePhaseSchema,
|
|
16175
|
+
number: external_exports.number().int().min(1)
|
|
16176
|
+
}).strict();
|
|
16177
|
+
var carriedFromEntrySchema = carriedPhaseSchema.extend({
|
|
16178
|
+
part: systemPartIdSchema
|
|
16179
|
+
}).strict();
|
|
15907
16180
|
var phasePlanRequestSchema = external_exports.object({
|
|
15908
16181
|
descriptionVersion: external_exports.number().int().min(1),
|
|
15909
16182
|
plan: external_exports.unknown()
|
|
@@ -15915,7 +16188,8 @@ var phasePlanRecordedSchema = external_exports.object({
|
|
|
15915
16188
|
state: plannedScopeStateSchema,
|
|
15916
16189
|
changedParts: external_exports.array(systemPartIdSchema).max(PHASE_PLAN_CHANGES_MAX),
|
|
15917
16190
|
typedApprovalOffered: external_exports.boolean(),
|
|
15918
|
-
reportedAt: utcIso8601
|
|
16191
|
+
reportedAt: utcIso8601,
|
|
16192
|
+
carriedFrom: external_exports.array(carriedFromEntrySchema).min(1).max(PHASE_PLAN_CHANGES_MAX).optional()
|
|
15919
16193
|
}).strict();
|
|
15920
16194
|
var phasePlanRefusedSchema = external_exports.object({
|
|
15921
16195
|
outcome: external_exports.literal("refused"),
|
|
@@ -15924,10 +16198,23 @@ var phasePlanRefusedSchema = external_exports.object({
|
|
|
15924
16198
|
var phasePlanUnavailableSchema = external_exports.object({
|
|
15925
16199
|
outcome: external_exports.literal("unavailable")
|
|
15926
16200
|
}).strict();
|
|
16201
|
+
var REMOVAL_OWED_ACTS = ["dropped", "removed"];
|
|
16202
|
+
var removalOwedPartSchema = external_exports.object({
|
|
16203
|
+
part: systemPartIdSchema,
|
|
16204
|
+
name: systemPartSchema.shape.name,
|
|
16205
|
+
phase: scopePhaseSchema,
|
|
16206
|
+
number: external_exports.number().int().min(1),
|
|
16207
|
+
act: external_exports.enum(REMOVAL_OWED_ACTS)
|
|
16208
|
+
}).strict();
|
|
16209
|
+
var phasePlanRemovalOwedSchema = external_exports.object({
|
|
16210
|
+
outcome: external_exports.literal("removal-owed"),
|
|
16211
|
+
parts: external_exports.array(removalOwedPartSchema).min(1).max(SYSTEM_PARTS_MAX)
|
|
16212
|
+
}).strict();
|
|
15927
16213
|
var phasePlanResultSchema = external_exports.discriminatedUnion("outcome", [
|
|
15928
16214
|
phasePlanRecordedSchema,
|
|
15929
16215
|
phasePlanRefusedSchema,
|
|
15930
|
-
phasePlanUnavailableSchema
|
|
16216
|
+
phasePlanUnavailableSchema,
|
|
16217
|
+
phasePlanRemovalOwedSchema
|
|
15931
16218
|
]);
|
|
15932
16219
|
var scopeApproveRequestSchema = external_exports.object({
|
|
15933
16220
|
act: external_exports.literal("approve"),
|
|
@@ -15989,7 +16276,8 @@ var scopeReadPartSchema = external_exports.object({
|
|
|
15989
16276
|
change: phasePlanChangeSchema,
|
|
15990
16277
|
summary: phasePlanSummarySchema,
|
|
15991
16278
|
status: scopePartStatusSchema,
|
|
15992
|
-
changed: external_exports.boolean()
|
|
16279
|
+
changed: external_exports.boolean(),
|
|
16280
|
+
carriedFrom: carriedPhaseSchema.optional()
|
|
15993
16281
|
}).strict();
|
|
15994
16282
|
var scopeReadDecisionSchema = external_exports.object({
|
|
15995
16283
|
act: scopeActSchema,
|
|
@@ -16016,9 +16304,580 @@ var scopeReadPlannedSchema = external_exports.object({
|
|
|
16016
16304
|
}).strict();
|
|
16017
16305
|
var scopeReadSchema = external_exports.union([scopeReadNotPlannedSchema, scopeReadPlannedSchema]);
|
|
16018
16306
|
|
|
16307
|
+
// ../events/dist/code-link.js
|
|
16308
|
+
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" });
|
|
16309
|
+
var codeLinkFactsSchema = external_exports.object({
|
|
16310
|
+
repositoryAddress: codeLinkAddressSchema.nullable(),
|
|
16311
|
+
headOnGitHub: external_exports.boolean()
|
|
16312
|
+
}).strict().refine((v) => !(v.headOnGitHub && v.repositoryAddress === null), {
|
|
16313
|
+
message: "cannot be true when the code is not on GitHub",
|
|
16314
|
+
path: ["headOnGitHub"]
|
|
16315
|
+
});
|
|
16316
|
+
var PART_CODE_LINK_SCOPES = ["part", "phase"];
|
|
16317
|
+
var PART_CODE_LINK_RANGE_REASONS = ["not-github", "not-pushed"];
|
|
16318
|
+
var PART_CODE_LINK_URL_PREFIX = "https://github.com/";
|
|
16319
|
+
var partCodeLinkGithubSchema = external_exports.object({
|
|
16320
|
+
kind: external_exports.literal("github"),
|
|
16321
|
+
scope: external_exports.enum(PART_CODE_LINK_SCOPES),
|
|
16322
|
+
url: external_exports.string().startsWith(PART_CODE_LINK_URL_PREFIX, { message: "must start with https://github.com/" })
|
|
16323
|
+
}).strict();
|
|
16324
|
+
var partCodeLinkRangeSchema = external_exports.object({
|
|
16325
|
+
kind: external_exports.literal("range"),
|
|
16326
|
+
why: external_exports.enum(PART_CODE_LINK_RANGE_REASONS),
|
|
16327
|
+
from: closeCommitSchema,
|
|
16328
|
+
to: closeCommitSchema
|
|
16329
|
+
}).strict();
|
|
16330
|
+
var partCodeLinkNoneSchema = external_exports.object({
|
|
16331
|
+
kind: external_exports.literal("none")
|
|
16332
|
+
}).strict();
|
|
16333
|
+
var partCodeLinkSchema = external_exports.discriminatedUnion("kind", [
|
|
16334
|
+
partCodeLinkGithubSchema,
|
|
16335
|
+
partCodeLinkRangeSchema,
|
|
16336
|
+
partCodeLinkNoneSchema
|
|
16337
|
+
]);
|
|
16338
|
+
|
|
16339
|
+
// ../events/dist/observed-changes.js
|
|
16340
|
+
var OBSERVED_PATHS_LISTED_MAX = 50;
|
|
16341
|
+
var OBSERVED_PATH_MAX = 300;
|
|
16342
|
+
var OBSERVED_COUNT_MAX = 1e6;
|
|
16343
|
+
var METHOD_FILE_PATTERNS = [
|
|
16344
|
+
"CLAUDE.md",
|
|
16345
|
+
"AGENTS.md",
|
|
16346
|
+
".claude/context",
|
|
16347
|
+
".claude/commands",
|
|
16348
|
+
".claude/hooks",
|
|
16349
|
+
".claude/settings.json",
|
|
16350
|
+
".mcp.json",
|
|
16351
|
+
".halfcycle",
|
|
16352
|
+
".workbench",
|
|
16353
|
+
"docs/product-spec.md",
|
|
16354
|
+
"docs/architecture-overview.md",
|
|
16355
|
+
"docs/invariants.md",
|
|
16356
|
+
"docs/project-log.md",
|
|
16357
|
+
"docs/code-map.md",
|
|
16358
|
+
"docs/system.json",
|
|
16359
|
+
"docs/diagrams",
|
|
16360
|
+
"docs/features",
|
|
16361
|
+
"docs/phases",
|
|
16362
|
+
"docs/build-records",
|
|
16363
|
+
"docs/decisions",
|
|
16364
|
+
"docs/components"
|
|
16365
|
+
];
|
|
16366
|
+
var TEST_FILE_PATTERNS = [
|
|
16367
|
+
"**/*.test.*",
|
|
16368
|
+
"**/*.spec.*",
|
|
16369
|
+
"**/*_test.*",
|
|
16370
|
+
"**/*_spec.*",
|
|
16371
|
+
"**/test_*.*",
|
|
16372
|
+
"**/*Test.java",
|
|
16373
|
+
"**/*Tests.java",
|
|
16374
|
+
"**/*Test.kt",
|
|
16375
|
+
"**/*Tests.cs",
|
|
16376
|
+
"**/*Test.php"
|
|
16377
|
+
];
|
|
16378
|
+
function isMethodFile(path) {
|
|
16379
|
+
return METHOD_FILE_PATTERNS.some((pattern) => patternOwns(pattern, path));
|
|
16380
|
+
}
|
|
16381
|
+
function isTestFile(path) {
|
|
16382
|
+
return TEST_FILE_PATTERNS.some((pattern) => patternOwns(pattern, path));
|
|
16383
|
+
}
|
|
16384
|
+
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}` });
|
|
16385
|
+
var observedDescriptionVersionSchema = external_exports.number().int().min(1);
|
|
16386
|
+
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` });
|
|
16387
|
+
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" });
|
|
16388
|
+
var observedMethodFilesSchema = external_exports.object({
|
|
16389
|
+
added: observedCountSchema,
|
|
16390
|
+
changed: observedCountSchema,
|
|
16391
|
+
removed: observedCountSchema
|
|
16392
|
+
}).strict();
|
|
16393
|
+
var observedFilesSchema = external_exports.object({
|
|
16394
|
+
added: observedCountSchema,
|
|
16395
|
+
changed: observedCountSchema,
|
|
16396
|
+
removed: observedCountSchema,
|
|
16397
|
+
testFilesAdded: observedCountSchema
|
|
16398
|
+
}).strict().refine((v) => v.testFilesAdded <= v.added, {
|
|
16399
|
+
message: "cannot be more than the files added",
|
|
16400
|
+
path: ["testFilesAdded"]
|
|
16401
|
+
});
|
|
16402
|
+
function filesChanged(files) {
|
|
16403
|
+
return files.added + files.changed + files.removed;
|
|
16404
|
+
}
|
|
16405
|
+
function pathsFitTheCount(files, paths) {
|
|
16406
|
+
return files === null ? paths.length === 0 : paths.length <= filesChanged(files);
|
|
16407
|
+
}
|
|
16408
|
+
function checkPartsUnique(parts, ctx, at) {
|
|
16409
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16410
|
+
parts.forEach((entry, i) => {
|
|
16411
|
+
if (seen.has(entry.part)) {
|
|
16412
|
+
ctx.addIssue({ code: "custom", path: [...at, i, "part"], message: `names the part "${entry.part}" twice` });
|
|
16413
|
+
}
|
|
16414
|
+
seen.add(entry.part);
|
|
16415
|
+
});
|
|
16416
|
+
}
|
|
16417
|
+
var observedBasisCountSchema = external_exports.object({
|
|
16418
|
+
outcome: external_exports.literal("count"),
|
|
16419
|
+
phase: phaseNameSchema,
|
|
16420
|
+
startCommit: closeCommitSchema,
|
|
16421
|
+
descriptionVersion: observedDescriptionVersionSchema,
|
|
16422
|
+
description: systemDescriptionSchema
|
|
16423
|
+
}).strict();
|
|
16424
|
+
var observedBasisCannotCountSchema = external_exports.object({
|
|
16425
|
+
outcome: external_exports.literal("cannot-count"),
|
|
16426
|
+
phase: phaseNameSchema,
|
|
16427
|
+
reason: external_exports.literal("no-start-commit")
|
|
16428
|
+
}).strict();
|
|
16429
|
+
var observedBasisNothingToCountSchema = external_exports.object({
|
|
16430
|
+
outcome: external_exports.literal("nothing-to-count"),
|
|
16431
|
+
reason: external_exports.enum(["no-open-phase", "no-description"])
|
|
16432
|
+
}).strict();
|
|
16433
|
+
var observedBasisSchema = external_exports.discriminatedUnion("outcome", [
|
|
16434
|
+
observedBasisCountSchema,
|
|
16435
|
+
observedBasisCannotCountSchema,
|
|
16436
|
+
observedBasisNothingToCountSchema
|
|
16437
|
+
]);
|
|
16438
|
+
var observedReportPartSchema = external_exports.object({
|
|
16439
|
+
part: systemPartIdSchema,
|
|
16440
|
+
added: observedCountSchema,
|
|
16441
|
+
changed: observedCountSchema,
|
|
16442
|
+
removed: observedCountSchema,
|
|
16443
|
+
testFilesAdded: observedCountSchema
|
|
16444
|
+
}).strict().refine((v) => v.testFilesAdded <= v.added, {
|
|
16445
|
+
message: "cannot be more than the files added",
|
|
16446
|
+
path: ["testFilesAdded"]
|
|
16447
|
+
}).refine((v) => filesChanged(v) >= 1, {
|
|
16448
|
+
message: "a part is listed only when at least one file changed under it",
|
|
16449
|
+
path: ["added"]
|
|
16450
|
+
});
|
|
16451
|
+
var observedReportOutsideSchema = external_exports.object({
|
|
16452
|
+
added: observedCountSchema,
|
|
16453
|
+
changed: observedCountSchema,
|
|
16454
|
+
removed: observedCountSchema,
|
|
16455
|
+
testFilesAdded: observedCountSchema,
|
|
16456
|
+
paths: observedPathListSchema
|
|
16457
|
+
}).strict().refine((v) => v.testFilesAdded <= v.added, {
|
|
16458
|
+
message: "cannot be more than the files added",
|
|
16459
|
+
path: ["testFilesAdded"]
|
|
16460
|
+
}).refine((v) => pathsFitTheCount(v, v.paths), {
|
|
16461
|
+
message: "lists more paths than files changed",
|
|
16462
|
+
path: ["paths"]
|
|
16463
|
+
});
|
|
16464
|
+
var observedChangesReportSchema = external_exports.object({
|
|
16465
|
+
phase: phaseNameSchema,
|
|
16466
|
+
startCommit: closeCommitSchema,
|
|
16467
|
+
headCommit: closeCommitSchema,
|
|
16468
|
+
headCommittedAt: utcIso8601,
|
|
16469
|
+
commits: observedCountSchema,
|
|
16470
|
+
descriptionVersion: observedDescriptionVersionSchema,
|
|
16471
|
+
parts: external_exports.array(observedReportPartSchema).max(SYSTEM_PARTS_MAX, { message: `can list at most ${SYSTEM_PARTS_MAX} parts` }),
|
|
16472
|
+
methodFiles: observedMethodFilesSchema,
|
|
16473
|
+
outsideEveryPart: observedReportOutsideSchema,
|
|
16474
|
+
codeLink: codeLinkFactsSchema
|
|
16475
|
+
}).strict().superRefine((v, ctx) => checkPartsUnique(v.parts, ctx, ["parts"]));
|
|
16476
|
+
var observedSaidSchema = external_exports.object({
|
|
16477
|
+
title: buildTaskSchema.shape.title,
|
|
16478
|
+
agent: buildTaskSchema.shape.agent,
|
|
16479
|
+
whyOutsidePlan: buildTaskSchema.shape.whyOutsidePlan
|
|
16480
|
+
}).strict();
|
|
16481
|
+
var observedNotInPlanSchema = external_exports.object({
|
|
16482
|
+
part: systemPartIdSchema,
|
|
16483
|
+
name: systemPartSchema.shape.name,
|
|
16484
|
+
said: external_exports.array(observedSaidSchema).max(BUILD_SNAPSHOT_TASK_LIMIT)
|
|
16485
|
+
}).strict();
|
|
16486
|
+
var observedChangesRecordedSchema = external_exports.object({
|
|
16487
|
+
outcome: external_exports.literal("recorded"),
|
|
16488
|
+
notInPlan: external_exports.array(observedNotInPlanSchema).max(SYSTEM_PARTS_MAX).superRefine((parts, ctx) => checkPartsUnique(parts, ctx, [])),
|
|
16489
|
+
outside: external_exports.object({
|
|
16490
|
+
files: observedCountSchema,
|
|
16491
|
+
example: observedPathSchema.nullable()
|
|
16492
|
+
}).strict().refine((v) => v.example === null || v.files >= 1, {
|
|
16493
|
+
message: "names an example when no file changed",
|
|
16494
|
+
path: ["example"]
|
|
16495
|
+
})
|
|
16496
|
+
}).strict();
|
|
16497
|
+
var observedChangesKeptSchema = external_exports.object({
|
|
16498
|
+
outcome: external_exports.literal("kept")
|
|
16499
|
+
}).strict();
|
|
16500
|
+
var observedChangesReportResultSchema = external_exports.discriminatedUnion("outcome", [
|
|
16501
|
+
observedChangesRecordedSchema,
|
|
16502
|
+
observedChangesKeptSchema
|
|
16503
|
+
]);
|
|
16504
|
+
var OBSERVE_NOT_SENT_REASONS = [
|
|
16505
|
+
"not-configured",
|
|
16506
|
+
"nothing-to-count",
|
|
16507
|
+
"cannot-count",
|
|
16508
|
+
"start-not-here",
|
|
16509
|
+
"start-not-on-branch",
|
|
16510
|
+
"basis-moved",
|
|
16511
|
+
"refused",
|
|
16512
|
+
"unreachable"
|
|
16513
|
+
];
|
|
16514
|
+
var observeRunRecordedSchema = external_exports.object({ outcome: external_exports.literal("recorded") }).strict();
|
|
16515
|
+
var observeRunKeptSchema = external_exports.object({ outcome: external_exports.literal("kept") }).strict();
|
|
16516
|
+
var observeRunNotSentSchema = external_exports.object({
|
|
16517
|
+
outcome: external_exports.literal("not-sent"),
|
|
16518
|
+
reason: external_exports.enum(OBSERVE_NOT_SENT_REASONS)
|
|
16519
|
+
}).strict();
|
|
16520
|
+
var observeRunResultSchema = external_exports.discriminatedUnion("outcome", [
|
|
16521
|
+
observeRunRecordedSchema,
|
|
16522
|
+
observeRunKeptSchema,
|
|
16523
|
+
observeRunNotSentSchema
|
|
16524
|
+
]);
|
|
16525
|
+
var observedChecksFiredSchema = external_exports.array(external_exports.string().min(1)).refine((names) => names.every((name, i) => i === 0 || names[i - 1] < name), {
|
|
16526
|
+
message: "must be sorted, each name once"
|
|
16527
|
+
});
|
|
16528
|
+
var observedCountingSchema = external_exports.discriminatedUnion("state", [
|
|
16529
|
+
external_exports.object({
|
|
16530
|
+
state: external_exports.literal("counted"),
|
|
16531
|
+
startCommit: closeCommitSchema,
|
|
16532
|
+
startRecordedAt: utcIso8601,
|
|
16533
|
+
headCommit: closeCommitSchema,
|
|
16534
|
+
headCommittedAt: utcIso8601,
|
|
16535
|
+
commits: observedCountSchema,
|
|
16536
|
+
countedAt: utcIso8601
|
|
16537
|
+
}).strict(),
|
|
16538
|
+
external_exports.object({
|
|
16539
|
+
state: external_exports.literal("not-counted-yet"),
|
|
16540
|
+
startCommit: closeCommitSchema,
|
|
16541
|
+
startRecordedAt: utcIso8601
|
|
16542
|
+
}).strict(),
|
|
16543
|
+
external_exports.object({
|
|
16544
|
+
state: external_exports.literal("cannot-count")
|
|
16545
|
+
}).strict()
|
|
16546
|
+
]);
|
|
16547
|
+
var observedPartSchema = external_exports.object({
|
|
16548
|
+
part: systemPartIdSchema,
|
|
16549
|
+
files: observedFilesSchema.nullable(),
|
|
16550
|
+
checksFired: observedChecksFiredSchema
|
|
16551
|
+
}).strict().refine((v) => v.files !== null && filesChanged(v.files) >= 1 || v.checksFired.length >= 1, {
|
|
16552
|
+
message: "a part is listed only when files changed under it or a check fired on one",
|
|
16553
|
+
path: ["files"]
|
|
16554
|
+
});
|
|
16555
|
+
var observedOutsideSchema = external_exports.object({
|
|
16556
|
+
files: observedFilesSchema.nullable(),
|
|
16557
|
+
paths: observedPathListSchema,
|
|
16558
|
+
checksFired: observedChecksFiredSchema
|
|
16559
|
+
}).strict().refine((v) => pathsFitTheCount(v.files, v.paths), {
|
|
16560
|
+
message: "lists more paths than files changed",
|
|
16561
|
+
path: ["paths"]
|
|
16562
|
+
});
|
|
16563
|
+
var observedChangesSchema = external_exports.object({
|
|
16564
|
+
phase: phaseNameSchema,
|
|
16565
|
+
counting: observedCountingSchema,
|
|
16566
|
+
descriptionVersion: observedDescriptionVersionSchema.nullable(),
|
|
16567
|
+
parts: external_exports.array(observedPartSchema).max(SYSTEM_PARTS_MAX),
|
|
16568
|
+
methodFiles: observedMethodFilesSchema.nullable(),
|
|
16569
|
+
outsideEveryPart: observedOutsideSchema,
|
|
16570
|
+
codeLink: codeLinkFactsSchema.nullable()
|
|
16571
|
+
}).strict().superRefine((v, ctx) => {
|
|
16572
|
+
checkPartsUnique(v.parts, ctx, ["parts"]);
|
|
16573
|
+
const counted = v.counting.state === "counted";
|
|
16574
|
+
const rule = counted ? "must be present when the phase is counted" : "must be null until the phase is counted";
|
|
16575
|
+
if (v.methodFiles !== null !== counted)
|
|
16576
|
+
ctx.addIssue({ code: "custom", path: ["methodFiles"], message: rule });
|
|
16577
|
+
if (v.outsideEveryPart.files !== null !== counted) {
|
|
16578
|
+
ctx.addIssue({ code: "custom", path: ["outsideEveryPart", "files"], message: rule });
|
|
16579
|
+
}
|
|
16580
|
+
if (v.codeLink !== null !== counted)
|
|
16581
|
+
ctx.addIssue({ code: "custom", path: ["codeLink"], message: rule });
|
|
16582
|
+
v.parts.forEach((entry, i) => {
|
|
16583
|
+
if (entry.files !== null !== counted)
|
|
16584
|
+
ctx.addIssue({ code: "custom", path: ["parts", i, "files"], message: rule });
|
|
16585
|
+
});
|
|
16586
|
+
if (counted && v.descriptionVersion === null) {
|
|
16587
|
+
ctx.addIssue({ code: "custom", path: ["descriptionVersion"], message: "must name the version the counts used" });
|
|
16588
|
+
}
|
|
16589
|
+
});
|
|
16590
|
+
var partOutsideThePlanSchema = external_exports.object({
|
|
16591
|
+
part: systemPartIdSchema,
|
|
16592
|
+
name: systemPartSchema.shape.name,
|
|
16593
|
+
seen: observedFilesSchema.nullable(),
|
|
16594
|
+
tasks: external_exports.array(observedSaidSchema.extend({ id: buildTaskSchema.shape.id }).strict()).max(BUILD_SNAPSHOT_TASK_LIMIT)
|
|
16595
|
+
}).strict();
|
|
16596
|
+
var NO_TASK_SAID_LINE = "No task said it changed this part.";
|
|
16597
|
+
function agentSaid(task) {
|
|
16598
|
+
const { title, agent, whyOutsidePlan } = task;
|
|
16599
|
+
if (whyOutsidePlan === void 0)
|
|
16600
|
+
return `"${title}" said it built this part, and gave no reason.`;
|
|
16601
|
+
if (agent === HUMAN_AGENT)
|
|
16602
|
+
return `The person on "${title}" said: "${whyOutsidePlan}"`;
|
|
16603
|
+
if (agent === void 0)
|
|
16604
|
+
return `The agent on "${title}" said: "${whyOutsidePlan}"`;
|
|
16605
|
+
return `${agent} said, on "${title}": "${whyOutsidePlan}"`;
|
|
16606
|
+
}
|
|
16607
|
+
|
|
16608
|
+
// ../events/dist/phase-review.js
|
|
16609
|
+
var BUILT_PART_ACTS = Object.freeze(["accept", "question", "send-back"]);
|
|
16610
|
+
var NOT_BUILT_PART_ACTS = Object.freeze(["carry", "drop"]);
|
|
16611
|
+
var REVIEW_ACTS = Object.freeze([...BUILT_PART_ACTS, ...NOT_BUILT_PART_ACTS]);
|
|
16612
|
+
var reviewActSchema = external_exports.enum(REVIEW_ACTS);
|
|
16613
|
+
function reviewActsFor(built) {
|
|
16614
|
+
return built ? BUILT_PART_ACTS : NOT_BUILT_PART_ACTS;
|
|
16615
|
+
}
|
|
16616
|
+
var REVIEW_NOTE_MAX = SCOPE_NOTE_MAX;
|
|
16617
|
+
var reviewNoteWireSchema = external_exports.string().max(REVIEW_NOTE_MAX);
|
|
16618
|
+
var reviewNoteSchema = external_exports.string().min(1).max(REVIEW_NOTE_MAX);
|
|
16619
|
+
var REVIEW_ANSWER_MAX = 1e3;
|
|
16620
|
+
var reviewAgentAnswerSchema = external_exports.string().min(1).max(REVIEW_ANSWER_MAX);
|
|
16621
|
+
var REVIEW_PARTS_MAX = PHASE_PLAN_CHANGES_MAX * 2;
|
|
16622
|
+
var REVIEW_PART_CHANGES = Object.freeze([...PHASE_PLAN_CHANGE_KINDS, "not-in-plan"]);
|
|
16623
|
+
var reviewPartChangeSchema = external_exports.enum(REVIEW_PART_CHANGES);
|
|
16624
|
+
var REVIEW_DECISIONS = Object.freeze(["accepted", "carried", "dropped"]);
|
|
16625
|
+
var reviewDecisionSchema = external_exports.enum(REVIEW_DECISIONS);
|
|
16626
|
+
function actOf(decision) {
|
|
16627
|
+
if (decision === "accepted")
|
|
16628
|
+
return "accept";
|
|
16629
|
+
if (decision === "carried")
|
|
16630
|
+
return "carry";
|
|
16631
|
+
return "drop";
|
|
16632
|
+
}
|
|
16633
|
+
var reviewCarriedToSchema = carriedPhaseSchema.nullable();
|
|
16634
|
+
var reviewStepNameSchema = stepNameSchema.refine((v) => !stepIdSchema.safeParse(v.trim()).success, {
|
|
16635
|
+
message: "must be the step's plain name"
|
|
16636
|
+
});
|
|
16637
|
+
var reviewStepSchema = external_exports.object({
|
|
16638
|
+
name: reviewStepNameSchema.nullable(),
|
|
16639
|
+
state: unfinishedStepSchema.shape.state,
|
|
16640
|
+
judged: external_exports.boolean()
|
|
16641
|
+
}).strict();
|
|
16642
|
+
var UNNAMED_STEP = "a step of this phase";
|
|
16643
|
+
function stepCalled(step) {
|
|
16644
|
+
return step.name === null ? UNNAMED_STEP : `"${step.name}"`;
|
|
16645
|
+
}
|
|
16646
|
+
function sentenceStart(text) {
|
|
16647
|
+
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
16648
|
+
}
|
|
16649
|
+
function unfinishedStepLabel(step) {
|
|
16650
|
+
const state = step.state === "parked" ? "parked" : "attempted, and did not finish";
|
|
16651
|
+
return `${stepCalled(step)} (${state})`;
|
|
16652
|
+
}
|
|
16653
|
+
function unfinishedStepLine(step) {
|
|
16654
|
+
if (step.judged) {
|
|
16655
|
+
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.`;
|
|
16656
|
+
}
|
|
16657
|
+
return `Finish ${stepCalled(step)} in your coding session, or record it as declined.`;
|
|
16658
|
+
}
|
|
16659
|
+
var openReviewRequestSchema = external_exports.object({
|
|
16660
|
+
actor: closeActorSchema,
|
|
16661
|
+
acceptanceOutcome: acceptanceOutcomeSchema,
|
|
16662
|
+
statements: closeStatementsSchema
|
|
16663
|
+
}).strict();
|
|
16664
|
+
var REVIEW_OPEN_TASKS_SHOWN_MAX = 10;
|
|
16665
|
+
var reviewOpenedSchema = external_exports.object({
|
|
16666
|
+
outcome: external_exports.literal("opened"),
|
|
16667
|
+
round: external_exports.number().int().min(1),
|
|
16668
|
+
parts: external_exports.number().int().min(0).max(REVIEW_PARTS_MAX),
|
|
16669
|
+
notBuilt: external_exports.number().int().min(0).max(REVIEW_PARTS_MAX),
|
|
16670
|
+
counted: external_exports.boolean(),
|
|
16671
|
+
typedReviewOffered: external_exports.boolean()
|
|
16672
|
+
}).strict().refine((v) => v.notBuilt <= v.parts, { message: "cannot exceed the parts in the round", path: ["notBuilt"] });
|
|
16673
|
+
var reviewTasksOpenSchema = external_exports.object({
|
|
16674
|
+
outcome: external_exports.literal("tasks-open"),
|
|
16675
|
+
open: external_exports.array(buildTaskSchema.shape.title).min(1).max(REVIEW_OPEN_TASKS_SHOWN_MAX),
|
|
16676
|
+
more: external_exports.number().int().min(0).max(BUILD_SNAPSHOT_TASK_LIMIT)
|
|
16677
|
+
}).strict();
|
|
16678
|
+
var reviewOutstandingSchema = external_exports.object({
|
|
16679
|
+
outcome: external_exports.literal("outstanding"),
|
|
16680
|
+
steps: external_exports.array(reviewStepSchema).min(1)
|
|
16681
|
+
}).strict();
|
|
16682
|
+
var reviewCleanOverFindingsSchema = external_exports.object({
|
|
16683
|
+
outcome: external_exports.literal("clean-over-findings"),
|
|
16684
|
+
findings: external_exports.number().int().min(1)
|
|
16685
|
+
}).strict();
|
|
16686
|
+
var reviewRefusedSchema = external_exports.object({
|
|
16687
|
+
outcome: external_exports.literal("refused"),
|
|
16688
|
+
problems: external_exports.array(systemDescriptionProblemSchema).min(1)
|
|
16689
|
+
}).strict();
|
|
16690
|
+
function bareReviewOutcome(outcome) {
|
|
16691
|
+
return external_exports.object({ outcome: external_exports.literal(outcome) }).strict();
|
|
16692
|
+
}
|
|
16693
|
+
var openReviewResultSchema = external_exports.discriminatedUnion("outcome", [
|
|
16694
|
+
reviewOpenedSchema,
|
|
16695
|
+
reviewTasksOpenSchema,
|
|
16696
|
+
bareReviewOutcome("no-build"),
|
|
16697
|
+
bareReviewOutcome("review-open"),
|
|
16698
|
+
reviewOutstandingSchema,
|
|
16699
|
+
reviewCleanOverFindingsSchema,
|
|
16700
|
+
reviewRefusedSchema,
|
|
16701
|
+
bareReviewOutcome("unavailable")
|
|
16702
|
+
]);
|
|
16703
|
+
var reviewPhaseNotOpenSchema = external_exports.object({
|
|
16704
|
+
statusCode: external_exports.literal(409),
|
|
16705
|
+
error: external_exports.literal("PhaseNotOpen"),
|
|
16706
|
+
message: external_exports.string().min(1),
|
|
16707
|
+
openPhase: external_exports.string().nullable()
|
|
16708
|
+
}).strict();
|
|
16709
|
+
var openReviewAnswerSchema = external_exports.union([openReviewResultSchema, reviewPhaseNotOpenSchema]);
|
|
16710
|
+
var reviewDecisionEntrySchema = external_exports.object({
|
|
16711
|
+
part: systemPartIdSchema,
|
|
16712
|
+
act: reviewActSchema,
|
|
16713
|
+
note: reviewNoteWireSchema.optional()
|
|
16714
|
+
}).strict();
|
|
16715
|
+
var reviewDecisionRequestSchema = external_exports.object({
|
|
16716
|
+
reviewVersion: external_exports.number().int().min(0),
|
|
16717
|
+
decisions: external_exports.array(reviewDecisionEntrySchema).max(REVIEW_PARTS_MAX)
|
|
16718
|
+
}).strict();
|
|
16719
|
+
var reviewDecidedSchema = external_exports.object({
|
|
16720
|
+
part: systemPartIdSchema,
|
|
16721
|
+
name: systemPartSchema.shape.name,
|
|
16722
|
+
act: reviewActSchema,
|
|
16723
|
+
carriedTo: reviewCarriedToSchema.optional()
|
|
16724
|
+
}).strict().refine((v) => v.carriedTo !== void 0 === (v.act === "carry"), {
|
|
16725
|
+
message: "is present exactly on a carry",
|
|
16726
|
+
path: ["carriedTo"]
|
|
16727
|
+
});
|
|
16728
|
+
var reviewRecordedSchema = external_exports.object({
|
|
16729
|
+
outcome: external_exports.literal("recorded"),
|
|
16730
|
+
decided: external_exports.array(reviewDecidedSchema).max(REVIEW_PARTS_MAX),
|
|
16731
|
+
waiting: external_exports.number().int().min(0).max(REVIEW_PARTS_MAX),
|
|
16732
|
+
closed: external_exports.boolean(),
|
|
16733
|
+
reviewVersion: external_exports.number().int().min(1)
|
|
16734
|
+
}).strict().superRefine((v, ctx) => {
|
|
16735
|
+
if (v.closed && v.waiting !== 0) {
|
|
16736
|
+
ctx.addIssue({ code: "custom", path: ["waiting"], message: "must be 0 once the phase is closed" });
|
|
16737
|
+
}
|
|
16738
|
+
if (v.decided.length === 0 && !v.closed) {
|
|
16739
|
+
ctx.addIssue({ code: "custom", path: ["decided"], message: "may be empty only when the phase closed" });
|
|
16740
|
+
}
|
|
16741
|
+
});
|
|
16742
|
+
var REVIEW_NOT_DECIDABLE_WHY = Object.freeze(["settled", "question-waiting", "sent-back"]);
|
|
16743
|
+
var reviewNotDecidablePartSchema = external_exports.object({
|
|
16744
|
+
part: systemPartIdSchema,
|
|
16745
|
+
name: systemPartSchema.shape.name,
|
|
16746
|
+
why: external_exports.enum(REVIEW_NOT_DECIDABLE_WHY)
|
|
16747
|
+
}).strict();
|
|
16748
|
+
var reviewNotDecidableSchema = external_exports.object({
|
|
16749
|
+
outcome: external_exports.literal("not-decidable"),
|
|
16750
|
+
parts: external_exports.array(reviewNotDecidablePartSchema).min(1).max(REVIEW_PARTS_MAX)
|
|
16751
|
+
}).strict();
|
|
16752
|
+
var reviewCloseRefusedSchema = external_exports.object({
|
|
16753
|
+
outcome: external_exports.literal("close-refused"),
|
|
16754
|
+
steps: external_exports.array(reviewStepSchema)
|
|
16755
|
+
}).strict();
|
|
16756
|
+
var reviewWalkChangedSchema = external_exports.object({
|
|
16757
|
+
outcome: external_exports.literal("walk-changed"),
|
|
16758
|
+
findings: external_exports.number().int().min(1)
|
|
16759
|
+
}).strict();
|
|
16760
|
+
var reviewDecisionResultSchema = external_exports.discriminatedUnion("outcome", [
|
|
16761
|
+
reviewRecordedSchema,
|
|
16762
|
+
bareReviewOutcome("review-changed"),
|
|
16763
|
+
bareReviewOutcome("no-review"),
|
|
16764
|
+
reviewNotDecidableSchema,
|
|
16765
|
+
reviewCloseRefusedSchema,
|
|
16766
|
+
reviewWalkChangedSchema,
|
|
16767
|
+
bareReviewOutcome("not-owner"),
|
|
16768
|
+
bareReviewOutcome("no-owner"),
|
|
16769
|
+
reviewRefusedSchema,
|
|
16770
|
+
bareReviewOutcome("unavailable")
|
|
16771
|
+
]);
|
|
16772
|
+
var reviewSentBackSchema = external_exports.object({
|
|
16773
|
+
round: external_exports.number().int().min(1),
|
|
16774
|
+
note: reviewNoteSchema
|
|
16775
|
+
}).strict();
|
|
16776
|
+
var reviewQuestionSchema = external_exports.object({
|
|
16777
|
+
question: reviewNoteSchema,
|
|
16778
|
+
agentAnswer: reviewAgentAnswerSchema.optional()
|
|
16779
|
+
}).strict();
|
|
16780
|
+
var reviewTaskSchema = external_exports.object({
|
|
16781
|
+
title: buildTaskSchema.shape.title,
|
|
16782
|
+
state: buildTaskStateSchema
|
|
16783
|
+
}).strict();
|
|
16784
|
+
var reviewViewPartSchema = external_exports.object({
|
|
16785
|
+
part: systemPartIdSchema,
|
|
16786
|
+
name: systemPartSchema.shape.name,
|
|
16787
|
+
change: reviewPartChangeSchema,
|
|
16788
|
+
built: external_exports.boolean(),
|
|
16789
|
+
builtWithoutApproval: external_exports.boolean(),
|
|
16790
|
+
decidable: external_exports.boolean(),
|
|
16791
|
+
waiting: external_exports.boolean(),
|
|
16792
|
+
decision: reviewDecisionSchema.optional(),
|
|
16793
|
+
reason: reviewNoteSchema.optional(),
|
|
16794
|
+
carriedTo: reviewCarriedToSchema.optional(),
|
|
16795
|
+
sentBack: external_exports.array(reviewSentBackSchema),
|
|
16796
|
+
questions: external_exports.array(reviewQuestionSchema),
|
|
16797
|
+
tasks: external_exports.array(reviewTaskSchema).max(BUILD_SNAPSHOT_TASK_LIMIT),
|
|
16798
|
+
said: external_exports.array(observedSaidSchema).max(BUILD_SNAPSHOT_TASK_LIMIT),
|
|
16799
|
+
codeLink: partCodeLinkSchema
|
|
16800
|
+
}).strict().superRefine((v, ctx) => {
|
|
16801
|
+
if (v.waiting !== (v.decision === void 0)) {
|
|
16802
|
+
ctx.addIssue({ code: "custom", path: ["waiting"], message: "must be true exactly when the part has no decision" });
|
|
16803
|
+
}
|
|
16804
|
+
if (v.decidable && !v.waiting) {
|
|
16805
|
+
ctx.addIssue({ code: "custom", path: ["decidable"], message: "cannot be true for a settled part" });
|
|
16806
|
+
}
|
|
16807
|
+
if (v.decision !== void 0 && !reviewActsFor(v.built).includes(actOf(v.decision))) {
|
|
16808
|
+
ctx.addIssue({ code: "custom", path: ["decision"], message: "is not an act this part takes" });
|
|
16809
|
+
}
|
|
16810
|
+
const carriedOrDropped = v.decision === "carried" || v.decision === "dropped";
|
|
16811
|
+
if (v.reason !== void 0 !== carriedOrDropped) {
|
|
16812
|
+
ctx.addIssue({ code: "custom", path: ["reason"], message: "is present exactly on a carry or a drop" });
|
|
16813
|
+
}
|
|
16814
|
+
if (v.carriedTo !== void 0 !== (v.decision === "carried")) {
|
|
16815
|
+
ctx.addIssue({ code: "custom", path: ["carriedTo"], message: "is present exactly on a carry" });
|
|
16816
|
+
}
|
|
16817
|
+
if (v.change === "not-in-plan" && !v.built) {
|
|
16818
|
+
ctx.addIssue({ code: "custom", path: ["built"], message: "is always true for a part outside the plan" });
|
|
16819
|
+
}
|
|
16820
|
+
});
|
|
16821
|
+
var reviewPhaseNumberSchema = external_exports.number().int().min(1).nullable();
|
|
16822
|
+
var phaseReviewNoneSchema = external_exports.object({
|
|
16823
|
+
state: external_exports.literal("none"),
|
|
16824
|
+
phase: scopePhaseSchema,
|
|
16825
|
+
number: reviewPhaseNumberSchema,
|
|
16826
|
+
closedWithoutReview: external_exports.boolean()
|
|
16827
|
+
}).strict();
|
|
16828
|
+
var reviewWalkSchema = external_exports.object({
|
|
16829
|
+
verdict: walkVerdictSchema,
|
|
16830
|
+
findings: external_exports.number().int().min(0)
|
|
16831
|
+
}).strict();
|
|
16832
|
+
function reviewedViewSchema(state) {
|
|
16833
|
+
return external_exports.object({
|
|
16834
|
+
state: external_exports.literal(state),
|
|
16835
|
+
phase: scopePhaseSchema,
|
|
16836
|
+
number: reviewPhaseNumberSchema,
|
|
16837
|
+
cycle: external_exports.number().int().min(0),
|
|
16838
|
+
round: external_exports.number().int().min(1),
|
|
16839
|
+
reviewVersion: external_exports.number().int().min(1),
|
|
16840
|
+
typedReviewOffered: external_exports.boolean(),
|
|
16841
|
+
counted: external_exports.boolean(),
|
|
16842
|
+
walk: reviewWalkSchema,
|
|
16843
|
+
parts: external_exports.array(reviewViewPartSchema).max(REVIEW_PARTS_MAX),
|
|
16844
|
+
observed: observedChangesSchema.nullable(),
|
|
16845
|
+
description: systemDescriptionSchema
|
|
16846
|
+
}).strict().superRefine((v, ctx) => {
|
|
16847
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16848
|
+
v.parts.forEach((p, i) => {
|
|
16849
|
+
if (seen.has(p.part))
|
|
16850
|
+
ctx.addIssue({ code: "custom", path: ["parts", i, "part"], message: "is listed twice" });
|
|
16851
|
+
seen.add(p.part);
|
|
16852
|
+
});
|
|
16853
|
+
const counted = v.observed !== null && v.observed.counting.state === "counted";
|
|
16854
|
+
if (v.counted !== counted) {
|
|
16855
|
+
ctx.addIssue({ code: "custom", path: ["counted"], message: "must be true exactly when the counts are counted" });
|
|
16856
|
+
}
|
|
16857
|
+
if (v.observed !== null && v.observed.phase !== v.phase) {
|
|
16858
|
+
ctx.addIssue({ code: "custom", path: ["observed", "phase"], message: "must be the phase under review" });
|
|
16859
|
+
}
|
|
16860
|
+
if (v.state === "closed") {
|
|
16861
|
+
v.parts.forEach((p, i) => {
|
|
16862
|
+
if (p.decision === void 0) {
|
|
16863
|
+
ctx.addIssue({ code: "custom", path: ["parts", i, "decision"], message: "every part of a closed review is decided" });
|
|
16864
|
+
}
|
|
16865
|
+
});
|
|
16866
|
+
}
|
|
16867
|
+
});
|
|
16868
|
+
}
|
|
16869
|
+
var phaseReviewOpenSchema = reviewedViewSchema("open");
|
|
16870
|
+
var phaseReviewClosedSchema = reviewedViewSchema("closed");
|
|
16871
|
+
var phaseReviewViewSchema = external_exports.discriminatedUnion("state", [
|
|
16872
|
+
phaseReviewNoneSchema,
|
|
16873
|
+
phaseReviewOpenSchema,
|
|
16874
|
+
phaseReviewClosedSchema
|
|
16875
|
+
]);
|
|
16876
|
+
|
|
16019
16877
|
// ../events/dist/client-version.js
|
|
16020
16878
|
var CLIENT_VERSION_HEADER = "x-halfcycle-client";
|
|
16021
16879
|
var TYPED_APPROVAL_OFF_SENTENCE = "Approving by typing is switched off for now.";
|
|
16880
|
+
var TYPED_REVIEW_OFF_SENTENCE = "Reviewing by typing is switched off for now.";
|
|
16022
16881
|
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.`;
|
|
16023
16882
|
|
|
16024
16883
|
// ../events/dist/scope-notices.js
|
|
@@ -16068,7 +16927,7 @@ function sentBackNotice(entry) {
|
|
|
16068
16927
|
return [
|
|
16069
16928
|
`Before anything else: the project's owner sent the scope of phase ${entry.number}, "${entry.phase}", back with these notes.`,
|
|
16070
16929
|
...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
|
|
16930
|
+
`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.`
|
|
16072
16931
|
].join("\n");
|
|
16073
16932
|
}
|
|
16074
16933
|
function waitingNotice(entry, offerTyping) {
|
|
@@ -16086,6 +16945,42 @@ function scopeNoticeText(scope, options) {
|
|
|
16086
16945
|
};
|
|
16087
16946
|
}
|
|
16088
16947
|
|
|
16948
|
+
// ../events/dist/review-notices.js
|
|
16949
|
+
var reviewNoticeQuestionSchema = external_exports.object({
|
|
16950
|
+
phase: scopePhaseSchema,
|
|
16951
|
+
number: reviewPhaseNumberSchema,
|
|
16952
|
+
part: systemPartIdSchema,
|
|
16953
|
+
partName: systemPartSchema.shape.name,
|
|
16954
|
+
question: reviewNoteSchema
|
|
16955
|
+
}).strict();
|
|
16956
|
+
var reviewNoticeSentBackSchema = external_exports.object({
|
|
16957
|
+
phase: scopePhaseSchema,
|
|
16958
|
+
number: reviewPhaseNumberSchema,
|
|
16959
|
+
part: systemPartIdSchema,
|
|
16960
|
+
partName: systemPartSchema.shape.name,
|
|
16961
|
+
note: reviewNoteSchema
|
|
16962
|
+
}).strict();
|
|
16963
|
+
var reviewNoticesSchema = external_exports.object({
|
|
16964
|
+
questions: external_exports.array(reviewNoticeQuestionSchema).max(REVIEW_PARTS_MAX),
|
|
16965
|
+
sentBack: external_exports.array(reviewNoticeSentBackSchema).max(REVIEW_PARTS_MAX)
|
|
16966
|
+
}).strict().refine((v) => v.questions.length > 0 || v.sentBack.length > 0, {
|
|
16967
|
+
message: "carried only when a question waits or a part was sent back"
|
|
16968
|
+
});
|
|
16969
|
+
function phaseCalled(phase, number4) {
|
|
16970
|
+
return number4 === null ? `phase "${phase}"` : `phase ${number4}, "${phase}"`;
|
|
16971
|
+
}
|
|
16972
|
+
function questionNotice(entry) {
|
|
16973
|
+
const answer = `${HALFCYCLE_COMMAND} review ${quoteForShell(entry.phase)} --answer ${quoteForShell(entry.part)} "<your answer>"`;
|
|
16974
|
+
return `Before anything else: the project's owner asked about "${entry.partName}" in the review of ${phaseCalled(entry.phase, entry.number)}: ${entry.question} Answer it with ${answer}.`;
|
|
16975
|
+
}
|
|
16976
|
+
function sentBackNotice2(entry) {
|
|
16977
|
+
const where = entry.number === null ? `in phase "${entry.phase}"` : `in phase ${entry.number}, "${entry.phase}",`;
|
|
16978
|
+
return `Before anything else: the project's owner sent "${entry.partName}" ${where} back to building: ${entry.note} Add or reopen the tasks that build it. When they are finished, run the close step again.`;
|
|
16979
|
+
}
|
|
16980
|
+
function reviewNoticeText(review) {
|
|
16981
|
+
return [...review.questions.map(questionNotice), ...review.sentBack.map(sentBackNotice2)];
|
|
16982
|
+
}
|
|
16983
|
+
|
|
16089
16984
|
// ../events/dist/device-auth.js
|
|
16090
16985
|
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");
|
|
16091
16986
|
var DEVICE_AUTH_STATUS = {
|
|
@@ -16273,6 +17168,56 @@ var chatScopeResultSchema = external_exports.discriminatedUnion("outcome", [
|
|
|
16273
17168
|
bareOutcome("unavailable")
|
|
16274
17169
|
]);
|
|
16275
17170
|
|
|
17171
|
+
// ../events/dist/review-chat.js
|
|
17172
|
+
var chatReviewAcceptRequestSchema = external_exports.object({
|
|
17173
|
+
act: external_exports.literal("accept"),
|
|
17174
|
+
reference: phaseIdentitySchema,
|
|
17175
|
+
reviewVersion: external_exports.number().int().min(0),
|
|
17176
|
+
parts: external_exports.array(systemPartIdSchema).max(REVIEW_PARTS_MAX)
|
|
17177
|
+
}).strict();
|
|
17178
|
+
function notedRequest(act) {
|
|
17179
|
+
return external_exports.object({
|
|
17180
|
+
act: external_exports.literal(act),
|
|
17181
|
+
reference: phaseIdentitySchema,
|
|
17182
|
+
reviewVersion: external_exports.number().int().min(0),
|
|
17183
|
+
part: systemPartIdSchema,
|
|
17184
|
+
note: reviewNoteWireSchema
|
|
17185
|
+
}).strict();
|
|
17186
|
+
}
|
|
17187
|
+
var chatReviewQuestionRequestSchema = notedRequest("question");
|
|
17188
|
+
var chatReviewSendBackRequestSchema = notedRequest("send-back");
|
|
17189
|
+
var chatReviewCarryRequestSchema = notedRequest("carry");
|
|
17190
|
+
var chatReviewDropRequestSchema = notedRequest("drop");
|
|
17191
|
+
var chatReviewRequestSchema = external_exports.discriminatedUnion("act", [
|
|
17192
|
+
chatReviewAcceptRequestSchema,
|
|
17193
|
+
chatReviewQuestionRequestSchema,
|
|
17194
|
+
chatReviewSendBackRequestSchema,
|
|
17195
|
+
chatReviewCarryRequestSchema,
|
|
17196
|
+
chatReviewDropRequestSchema
|
|
17197
|
+
]);
|
|
17198
|
+
function bareOutcome2(outcome) {
|
|
17199
|
+
return external_exports.object({ outcome: external_exports.literal(outcome) }).strict();
|
|
17200
|
+
}
|
|
17201
|
+
var chatReviewResultSchema = external_exports.discriminatedUnion("outcome", [
|
|
17202
|
+
...reviewDecisionResultSchema.options,
|
|
17203
|
+
bareOutcome2("show-first"),
|
|
17204
|
+
bareOutcome2("not-offered")
|
|
17205
|
+
]);
|
|
17206
|
+
var reviewAnswerRequestSchema = external_exports.object({
|
|
17207
|
+
part: systemPartIdSchema,
|
|
17208
|
+
answer: statementText(REVIEW_ANSWER_MAX)
|
|
17209
|
+
}).strict();
|
|
17210
|
+
var reviewAnswerRecordedSchema = external_exports.object({
|
|
17211
|
+
outcome: external_exports.literal("recorded"),
|
|
17212
|
+
name: systemPartSchema.shape.name
|
|
17213
|
+
}).strict();
|
|
17214
|
+
var reviewAnswerResultSchema = external_exports.discriminatedUnion("outcome", [
|
|
17215
|
+
reviewAnswerRecordedSchema,
|
|
17216
|
+
bareOutcome2("no-question"),
|
|
17217
|
+
reviewRefusedSchema,
|
|
17218
|
+
bareOutcome2("unavailable")
|
|
17219
|
+
]);
|
|
17220
|
+
|
|
16276
17221
|
// dist/hook-protocol.js
|
|
16277
17222
|
import { writeSync } from "node:fs";
|
|
16278
17223
|
function emitBlock(reason) {
|
|
@@ -16332,8 +17277,6 @@ function emitNeverConfigured(exitCode, reason, engagementId) {
|
|
|
16332
17277
|
writeSync(2, neverConfiguredReport(reason, void 0, engagementId));
|
|
16333
17278
|
process.exitCode = exitCode;
|
|
16334
17279
|
}
|
|
16335
|
-
var SIGN_IN_COMMAND = '"npx halfcycle"';
|
|
16336
|
-
var HOOK_REMEDY = `Run ${SIGN_IN_COMMAND} in the project folder to sign in.`;
|
|
16337
17280
|
function namesTheSignInCommand(text) {
|
|
16338
17281
|
return text.includes(SIGN_IN_COMMAND);
|
|
16339
17282
|
}
|
|
@@ -16660,13 +17603,16 @@ import { spawnSync } from "node:child_process";
|
|
|
16660
17603
|
import { existsSync, readFileSync } from "node:fs";
|
|
16661
17604
|
import { relative } from "node:path";
|
|
16662
17605
|
var COLOR_AWARE_SUBCOMMANDS = /* @__PURE__ */ new Set(["diff", "show", "log"]);
|
|
16663
|
-
function
|
|
17606
|
+
function spawnGit(args2, cwd, timeoutMs) {
|
|
16664
17607
|
const argv = COLOR_AWARE_SUBCOMMANDS.has(args2[0] ?? "") ? [args2[0], "--no-color", ...args2.slice(1)] : args2;
|
|
16665
|
-
|
|
17608
|
+
return spawnSync("git", argv, {
|
|
16666
17609
|
cwd,
|
|
16667
17610
|
encoding: "utf-8",
|
|
16668
17611
|
...timeoutMs === void 0 ? {} : { timeout: timeoutMs }
|
|
16669
17612
|
});
|
|
17613
|
+
}
|
|
17614
|
+
function git(args2, cwd, timeoutMs) {
|
|
17615
|
+
const result = spawnGit(args2, cwd, timeoutMs);
|
|
16670
17616
|
if (result.error) {
|
|
16671
17617
|
const missing = result.error.code === "ENOENT" && existsSync(cwd);
|
|
16672
17618
|
return { ok: false, message: `git error: ${result.error.message}`, ...missing ? { gitMissing: true } : {} };
|
|
@@ -16828,6 +17774,124 @@ function getWiringStatus(pathspecs, cwd) {
|
|
|
16828
17774
|
...pathspecs
|
|
16829
17775
|
], cwd, WIRING_STATUS_TIMEOUT_MS);
|
|
16830
17776
|
}
|
|
17777
|
+
var OBSERVE_GIT_TIMEOUT_MS = 5e3;
|
|
17778
|
+
function isFullCommit(value) {
|
|
17779
|
+
return closeCommitSchema.safeParse(value).success;
|
|
17780
|
+
}
|
|
17781
|
+
function notACommit(name) {
|
|
17782
|
+
return { ok: false, message: `${name} is not a full commit id` };
|
|
17783
|
+
}
|
|
17784
|
+
function headCommit(repoRoot) {
|
|
17785
|
+
const result = git(["rev-parse", "--verify", "HEAD^{commit}"], repoRoot, OBSERVE_GIT_TIMEOUT_MS);
|
|
17786
|
+
if (!result.ok)
|
|
17787
|
+
return result;
|
|
17788
|
+
const commit = result.value.trim();
|
|
17789
|
+
return isFullCommit(commit) ? { ok: true, value: commit } : notACommit("HEAD");
|
|
17790
|
+
}
|
|
17791
|
+
function headCommittedAt(head, repoRoot) {
|
|
17792
|
+
if (!isFullCommit(head))
|
|
17793
|
+
return notACommit("head");
|
|
17794
|
+
const result = git(["log", "-1", "--format=%cI", head], repoRoot, OBSERVE_GIT_TIMEOUT_MS);
|
|
17795
|
+
if (!result.ok)
|
|
17796
|
+
return result;
|
|
17797
|
+
const when = new Date(result.value.trim());
|
|
17798
|
+
if (Number.isNaN(when.getTime()))
|
|
17799
|
+
return { ok: false, message: "git gave no readable commit time" };
|
|
17800
|
+
const iso = when.toISOString();
|
|
17801
|
+
return utcIso8601.safeParse(iso).success ? { ok: true, value: iso } : { ok: false, message: "git gave no readable commit time" };
|
|
17802
|
+
}
|
|
17803
|
+
function isAncestor(start, head, repoRoot) {
|
|
17804
|
+
if (!isFullCommit(start))
|
|
17805
|
+
return notACommit("start");
|
|
17806
|
+
if (!isFullCommit(head))
|
|
17807
|
+
return notACommit("head");
|
|
17808
|
+
const result = spawnGit(["merge-base", "--is-ancestor", start, head], repoRoot, OBSERVE_GIT_TIMEOUT_MS);
|
|
17809
|
+
if (result.error)
|
|
17810
|
+
return { ok: false, message: `git error: ${result.error.message}` };
|
|
17811
|
+
if (result.status === 0)
|
|
17812
|
+
return { ok: true, value: true };
|
|
17813
|
+
if (result.status === 1)
|
|
17814
|
+
return { ok: true, value: false };
|
|
17815
|
+
const stderr = result.stderr?.trim() ?? "";
|
|
17816
|
+
return { ok: false, message: `git merge-base failed: ${stderr || "non-zero exit"}` };
|
|
17817
|
+
}
|
|
17818
|
+
function commitCount(start, head, repoRoot) {
|
|
17819
|
+
if (!isFullCommit(start))
|
|
17820
|
+
return notACommit("start");
|
|
17821
|
+
if (!isFullCommit(head))
|
|
17822
|
+
return notACommit("head");
|
|
17823
|
+
const result = git(["rev-list", "--count", `${start}..${head}`], repoRoot, OBSERVE_GIT_TIMEOUT_MS);
|
|
17824
|
+
if (!result.ok)
|
|
17825
|
+
return result;
|
|
17826
|
+
const text = result.value.trim();
|
|
17827
|
+
if (!/^\d+$/.test(text))
|
|
17828
|
+
return { ok: false, message: "git gave no readable commit count" };
|
|
17829
|
+
return { ok: true, value: Number(text) };
|
|
17830
|
+
}
|
|
17831
|
+
function rangeNameStatus(start, head, repoRoot) {
|
|
17832
|
+
if (!isFullCommit(start))
|
|
17833
|
+
return notACommit("start");
|
|
17834
|
+
if (!isFullCommit(head))
|
|
17835
|
+
return notACommit("head");
|
|
17836
|
+
const result = git(["diff", "--name-status", "--no-renames", "-z", start, head], repoRoot, OBSERVE_GIT_TIMEOUT_MS);
|
|
17837
|
+
if (!result.ok)
|
|
17838
|
+
return result;
|
|
17839
|
+
return parseNameStatusZ(result.value);
|
|
17840
|
+
}
|
|
17841
|
+
function parseNameStatusZ(output) {
|
|
17842
|
+
if (output === "")
|
|
17843
|
+
return { ok: true, value: [] };
|
|
17844
|
+
if (!output.endsWith("\0"))
|
|
17845
|
+
return { ok: false, message: "git gave an unreadable list of changed files" };
|
|
17846
|
+
const fields = output.slice(0, -1).split("\0");
|
|
17847
|
+
if (fields.length % 2 !== 0)
|
|
17848
|
+
return { ok: false, message: "git gave an unreadable list of changed files" };
|
|
17849
|
+
const entries = [];
|
|
17850
|
+
for (let i = 0; i < fields.length; i += 2) {
|
|
17851
|
+
const status = fields[i];
|
|
17852
|
+
const path = fields[i + 1];
|
|
17853
|
+
if (!/^[A-Z][0-9]*$/.test(status) || status.startsWith("R") || status.startsWith("C") || path === "") {
|
|
17854
|
+
return { ok: false, message: "git gave an unreadable list of changed files" };
|
|
17855
|
+
}
|
|
17856
|
+
entries.push({ status: status.charAt(0), path });
|
|
17857
|
+
}
|
|
17858
|
+
return { ok: true, value: entries };
|
|
17859
|
+
}
|
|
17860
|
+
function readCodeLinkFacts(repoRoot, head) {
|
|
17861
|
+
const nothing = { repositoryAddress: null, headOnGitHub: false };
|
|
17862
|
+
if (!isFullCommit(head))
|
|
17863
|
+
return nothing;
|
|
17864
|
+
try {
|
|
17865
|
+
const remote = currentRemote(repoRoot);
|
|
17866
|
+
const url2 = git(["remote", "get-url", "--", remote], repoRoot, OBSERVE_GIT_TIMEOUT_MS);
|
|
17867
|
+
if (!url2.ok)
|
|
17868
|
+
return nothing;
|
|
17869
|
+
const repositoryAddress = githubRepositoryAddress(url2.value.trim());
|
|
17870
|
+
if (repositoryAddress === null)
|
|
17871
|
+
return nothing;
|
|
17872
|
+
const refs = git(["for-each-ref", "--contains", head, "--format=%(refname)", `refs/remotes/${remote}/`], repoRoot, OBSERVE_GIT_TIMEOUT_MS);
|
|
17873
|
+
if (!refs.ok)
|
|
17874
|
+
return nothing;
|
|
17875
|
+
const facts = { repositoryAddress, headOnGitHub: refs.value.trim() !== "" };
|
|
17876
|
+
return codeLinkFactsSchema.safeParse(facts).success ? facts : nothing;
|
|
17877
|
+
} catch {
|
|
17878
|
+
return nothing;
|
|
17879
|
+
}
|
|
17880
|
+
}
|
|
17881
|
+
function currentRemote(repoRoot) {
|
|
17882
|
+
const symbolic = git(["symbolic-ref", "--quiet", "HEAD"], repoRoot, OBSERVE_GIT_TIMEOUT_MS);
|
|
17883
|
+
if (!symbolic.ok)
|
|
17884
|
+
return "origin";
|
|
17885
|
+
const ref = symbolic.value.trim();
|
|
17886
|
+
if (!ref.startsWith("refs/heads/"))
|
|
17887
|
+
return "origin";
|
|
17888
|
+
const branch = ref.slice("refs/heads/".length);
|
|
17889
|
+
const configured = git(["config", "--get", `branch.${branch}.remote`], repoRoot, OBSERVE_GIT_TIMEOUT_MS);
|
|
17890
|
+
if (!configured.ok)
|
|
17891
|
+
return "origin";
|
|
17892
|
+
const remote = configured.value.trim();
|
|
17893
|
+
return remote === "" || remote === "." ? "origin" : remote;
|
|
17894
|
+
}
|
|
16831
17895
|
|
|
16832
17896
|
// dist/screen/secret-shape.js
|
|
16833
17897
|
function screenExcerpts(excerpts) {
|
|
@@ -18495,7 +19559,10 @@ var SESSION_NOTICE_SUFFIX = {
|
|
|
18495
19559
|
"no-repository": ".norepo",
|
|
18496
19560
|
"credential-rejected": ".credential",
|
|
18497
19561
|
"credential-renewed-for-this-run": ".renewed",
|
|
18498
|
-
"credential-missing": ".nocredential"
|
|
19562
|
+
"credential-missing": ".nocredential",
|
|
19563
|
+
"observe-no-start-here": ".observe-nostart",
|
|
19564
|
+
"observe-not-on-branch": ".observe-nobranch",
|
|
19565
|
+
"observe-no-start-recorded": ".observe-norecord"
|
|
18499
19566
|
};
|
|
18500
19567
|
function firstNoticeThisSession(notice, folder, sessionId) {
|
|
18501
19568
|
if (!sessionId)
|
|
@@ -18590,6 +19657,14 @@ function orphanSweep(repoRoot, refKnown, keepSessionId, now = Date.now()) {
|
|
|
18590
19657
|
}
|
|
18591
19658
|
}
|
|
18592
19659
|
}
|
|
19660
|
+
function observeThrottlePath(folder) {
|
|
19661
|
+
return join6(markerDir(), `${MARKER_PREFIX}${repoHash(folder)}.observe-throttle`);
|
|
19662
|
+
}
|
|
19663
|
+
function observeNoticesPath(folder, sessionId) {
|
|
19664
|
+
if (!sessionId)
|
|
19665
|
+
return null;
|
|
19666
|
+
return join6(markerDir(), `${MARKER_PREFIX}${repoHash(folder)}-${sessionId}.observe-said`);
|
|
19667
|
+
}
|
|
18593
19668
|
|
|
18594
19669
|
// dist/seat-configuration.js
|
|
18595
19670
|
import { existsSync as existsSync5, readFileSync as readFileSync8, realpathSync as realpathSync2 } from "node:fs";
|
|
@@ -18923,9 +19998,11 @@ function collectDiff(repoRoot, base) {
|
|
|
18923
19998
|
getDiff: (relPath) => getWorkingTreeFileDiff(relPath, repoRoot)
|
|
18924
19999
|
};
|
|
18925
20000
|
}
|
|
18926
|
-
async function runSessionDiff(input, kind, readSeat = readSeatConfiguration) {
|
|
20001
|
+
async function runSessionDiff(input, kind, readSeat = readSeatConfiguration, afterPass) {
|
|
18927
20002
|
const reply = hookReply(STOP_HOOK_EVENT[kind]);
|
|
18928
20003
|
await evaluateSession(input, kind, reply, readSeat);
|
|
20004
|
+
if (afterPass)
|
|
20005
|
+
await afterPass(reply);
|
|
18929
20006
|
reply.flush();
|
|
18930
20007
|
}
|
|
18931
20008
|
async function evaluateSession(input, kind, reply, readSeat) {
|
|
@@ -19106,17 +20183,328 @@ async function runSubagentStop() {
|
|
|
19106
20183
|
await runSessionDiff(input, "subagent-stop");
|
|
19107
20184
|
}
|
|
19108
20185
|
|
|
20186
|
+
// dist/observed.js
|
|
20187
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "node:fs";
|
|
20188
|
+
function emptyTally() {
|
|
20189
|
+
return { added: 0, changed: 0, removed: 0, testFilesAdded: 0 };
|
|
20190
|
+
}
|
|
20191
|
+
function kindOf(status) {
|
|
20192
|
+
if (status === "A")
|
|
20193
|
+
return "added";
|
|
20194
|
+
if (status === "D")
|
|
20195
|
+
return "removed";
|
|
20196
|
+
return "changed";
|
|
20197
|
+
}
|
|
20198
|
+
function countObservedChanges(entries, description) {
|
|
20199
|
+
const methodFiles = { added: 0, changed: 0, removed: 0 };
|
|
20200
|
+
const byPart = /* @__PURE__ */ new Map();
|
|
20201
|
+
const outside = emptyTally();
|
|
20202
|
+
const paths = [];
|
|
20203
|
+
const listed = /* @__PURE__ */ new Set();
|
|
20204
|
+
for (const entry of entries) {
|
|
20205
|
+
const kind = kindOf(entry.status);
|
|
20206
|
+
if (isMethodFile(entry.path)) {
|
|
20207
|
+
methodFiles[kind] += 1;
|
|
20208
|
+
continue;
|
|
20209
|
+
}
|
|
20210
|
+
const owner = ownerOf(entry.path, description);
|
|
20211
|
+
let tally;
|
|
20212
|
+
if (owner === null) {
|
|
20213
|
+
tally = outside;
|
|
20214
|
+
if (entry.path.length >= 1 && entry.path.length <= OBSERVED_PATH_MAX && paths.length < OBSERVED_PATHS_LISTED_MAX && !listed.has(entry.path)) {
|
|
20215
|
+
paths.push(entry.path);
|
|
20216
|
+
listed.add(entry.path);
|
|
20217
|
+
}
|
|
20218
|
+
} else {
|
|
20219
|
+
tally = byPart.get(owner) ?? emptyTally();
|
|
20220
|
+
byPart.set(owner, tally);
|
|
20221
|
+
}
|
|
20222
|
+
tally[kind] += 1;
|
|
20223
|
+
if (kind === "added" && isTestFile(entry.path))
|
|
20224
|
+
tally.testFilesAdded += 1;
|
|
20225
|
+
}
|
|
20226
|
+
const parts = description.parts.filter((part) => byPart.has(part.id)).map((part) => ({ part: part.id, ...byPart.get(part.id) }));
|
|
20227
|
+
return { parts, methodFiles, outsideEveryPart: { ...outside, paths } };
|
|
20228
|
+
}
|
|
20229
|
+
var OBSERVE_MIN_INTERVAL_MS = 6e4;
|
|
20230
|
+
var OBSERVE_RECHECK_MS = 6e5;
|
|
20231
|
+
var OBSERVE_REQUEST_DEADLINE_MS = 1500;
|
|
20232
|
+
async function requestJson(fetchFn, url2, init) {
|
|
20233
|
+
const controller = new AbortController();
|
|
20234
|
+
const timer = setTimeout(() => controller.abort(), OBSERVE_REQUEST_DEADLINE_MS);
|
|
20235
|
+
try {
|
|
20236
|
+
const response = await fetchFn(url2, { ...init, signal: controller.signal });
|
|
20237
|
+
let body = null;
|
|
20238
|
+
try {
|
|
20239
|
+
body = await response.json();
|
|
20240
|
+
} catch {
|
|
20241
|
+
body = null;
|
|
20242
|
+
}
|
|
20243
|
+
if (controller.signal.aborted)
|
|
20244
|
+
return { kind: "unreachable" };
|
|
20245
|
+
return { kind: "answer", status: response.status, body };
|
|
20246
|
+
} catch {
|
|
20247
|
+
return { kind: "unreachable" };
|
|
20248
|
+
} finally {
|
|
20249
|
+
clearTimeout(timer);
|
|
20250
|
+
}
|
|
20251
|
+
}
|
|
20252
|
+
function refusalReason(status) {
|
|
20253
|
+
return status >= 500 ? "unreachable" : "refused";
|
|
20254
|
+
}
|
|
20255
|
+
async function attemptObservation(opts) {
|
|
20256
|
+
const { repoRoot, head, config: config2, controlOrigin, fetchFn } = opts;
|
|
20257
|
+
const base = `${controlOrigin}/engagements/${encodeURIComponent(config2.guardEngagementId)}/observed`;
|
|
20258
|
+
const authorization = `Bearer ${config2.guardServiceToken}`;
|
|
20259
|
+
const basisAnswer = await requestJson(fetchFn, `${base}/basis`, {
|
|
20260
|
+
method: "GET",
|
|
20261
|
+
headers: { Authorization: authorization }
|
|
20262
|
+
});
|
|
20263
|
+
if (basisAnswer.kind === "unreachable")
|
|
20264
|
+
return { outcome: "not-sent", reason: "unreachable" };
|
|
20265
|
+
if (basisAnswer.status < 200 || basisAnswer.status >= 300) {
|
|
20266
|
+
return { outcome: "not-sent", reason: refusalReason(basisAnswer.status) };
|
|
20267
|
+
}
|
|
20268
|
+
const parsedBasis = observedBasisSchema.safeParse(basisAnswer.body);
|
|
20269
|
+
if (!parsedBasis.success)
|
|
20270
|
+
return { outcome: "not-sent", reason: "refused" };
|
|
20271
|
+
const basis = parsedBasis.data;
|
|
20272
|
+
if (basis.outcome === "nothing-to-count")
|
|
20273
|
+
return { outcome: "not-sent", reason: "nothing-to-count" };
|
|
20274
|
+
if (basis.outcome === "cannot-count")
|
|
20275
|
+
return { outcome: "not-sent", reason: "cannot-count", phase: basis.phase };
|
|
20276
|
+
if (repoRoot === null || !refExists(basis.startCommit, repoRoot)) {
|
|
20277
|
+
return { outcome: "not-sent", reason: "start-not-here" };
|
|
20278
|
+
}
|
|
20279
|
+
if (head === null)
|
|
20280
|
+
return { outcome: "not-sent", reason: "start-not-on-branch" };
|
|
20281
|
+
const ancestor = isAncestor(basis.startCommit, head, repoRoot);
|
|
20282
|
+
if (!ancestor.ok)
|
|
20283
|
+
return { outcome: "not-sent", reason: "unreachable" };
|
|
20284
|
+
if (!ancestor.value)
|
|
20285
|
+
return { outcome: "not-sent", reason: "start-not-on-branch" };
|
|
20286
|
+
const commits = commitCount(basis.startCommit, head, repoRoot);
|
|
20287
|
+
const committedAt = headCommittedAt(head, repoRoot);
|
|
20288
|
+
const entries = rangeNameStatus(basis.startCommit, head, repoRoot);
|
|
20289
|
+
if (!commits.ok || !committedAt.ok || !entries.ok)
|
|
20290
|
+
return { outcome: "not-sent", reason: "unreachable" };
|
|
20291
|
+
const report = {
|
|
20292
|
+
phase: basis.phase,
|
|
20293
|
+
startCommit: basis.startCommit,
|
|
20294
|
+
headCommit: head,
|
|
20295
|
+
headCommittedAt: committedAt.value,
|
|
20296
|
+
commits: commits.value,
|
|
20297
|
+
descriptionVersion: basis.descriptionVersion,
|
|
20298
|
+
...countObservedChanges(entries.value, basis.description),
|
|
20299
|
+
codeLink: readCodeLinkFacts(repoRoot, head)
|
|
20300
|
+
};
|
|
20301
|
+
if (!observedChangesReportSchema.safeParse(report).success)
|
|
20302
|
+
return { outcome: "not-sent", reason: "refused" };
|
|
20303
|
+
const sent = await requestJson(fetchFn, base, {
|
|
20304
|
+
method: "POST",
|
|
20305
|
+
headers: { Authorization: authorization, "Content-Type": "application/json" },
|
|
20306
|
+
body: JSON.stringify(report)
|
|
20307
|
+
});
|
|
20308
|
+
if (sent.kind === "unreachable")
|
|
20309
|
+
return { outcome: "not-sent", reason: "unreachable" };
|
|
20310
|
+
if (sent.status === 409)
|
|
20311
|
+
return { outcome: "not-sent", reason: "basis-moved" };
|
|
20312
|
+
if (sent.status < 200 || sent.status >= 300)
|
|
20313
|
+
return { outcome: "not-sent", reason: refusalReason(sent.status) };
|
|
20314
|
+
const answer = observedChangesReportResultSchema.safeParse(sent.body);
|
|
20315
|
+
if (!answer.success)
|
|
20316
|
+
return { outcome: "not-sent", reason: "refused" };
|
|
20317
|
+
return answer.data.outcome === "kept" ? { outcome: "kept" } : { outcome: "recorded", answer: answer.data };
|
|
20318
|
+
}
|
|
20319
|
+
function readThrottle(folder) {
|
|
20320
|
+
try {
|
|
20321
|
+
const parsed = JSON.parse(readFileSync9(observeThrottlePath(folder), "utf-8"));
|
|
20322
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
20323
|
+
return null;
|
|
20324
|
+
const { attemptedAt, accepted } = parsed;
|
|
20325
|
+
if (typeof attemptedAt !== "number")
|
|
20326
|
+
return null;
|
|
20327
|
+
const sent = accepted;
|
|
20328
|
+
const usable = sent !== null && sent !== void 0 && typeof sent.head === "string" && typeof sent.at === "number";
|
|
20329
|
+
return { attemptedAt, accepted: usable ? { head: sent.head, at: sent.at } : null };
|
|
20330
|
+
} catch {
|
|
20331
|
+
return null;
|
|
20332
|
+
}
|
|
20333
|
+
}
|
|
20334
|
+
function writeThrottle(folder, state) {
|
|
20335
|
+
try {
|
|
20336
|
+
writeFileSync4(observeThrottlePath(folder), JSON.stringify(state), "utf-8");
|
|
20337
|
+
} catch {
|
|
20338
|
+
}
|
|
20339
|
+
}
|
|
20340
|
+
function throttleAllows(state, head, now) {
|
|
20341
|
+
if (state === null)
|
|
20342
|
+
return true;
|
|
20343
|
+
if (now - state.attemptedAt < OBSERVE_MIN_INTERVAL_MS)
|
|
20344
|
+
return false;
|
|
20345
|
+
if (state.accepted === null)
|
|
20346
|
+
return true;
|
|
20347
|
+
if (head === null || head !== state.accepted.head)
|
|
20348
|
+
return true;
|
|
20349
|
+
return now - state.accepted.at >= OBSERVE_RECHECK_MS;
|
|
20350
|
+
}
|
|
20351
|
+
function locate(cwd) {
|
|
20352
|
+
const root = getRepoRoot(cwd);
|
|
20353
|
+
const repoRoot = root.ok ? root.value : null;
|
|
20354
|
+
const read = repoRoot === null ? null : headCommit(repoRoot);
|
|
20355
|
+
return { repoRoot, folder: repoRoot ?? cwd, head: read !== null && read.ok ? read.value : null };
|
|
20356
|
+
}
|
|
20357
|
+
function recordAttempt(folder, prior, head, at, attempt2) {
|
|
20358
|
+
const accepted = (attempt2.outcome === "recorded" || attempt2.outcome === "kept") && head !== null ? { head, at } : prior?.accepted ?? null;
|
|
20359
|
+
writeThrottle(folder, { attemptedAt: at, accepted });
|
|
20360
|
+
}
|
|
20361
|
+
async function runObserve(deps = {}) {
|
|
20362
|
+
let result;
|
|
20363
|
+
try {
|
|
20364
|
+
const configured = readConfig();
|
|
20365
|
+
if (!configured.config) {
|
|
20366
|
+
result = { outcome: "not-sent", reason: "not-configured" };
|
|
20367
|
+
} else {
|
|
20368
|
+
const now = deps.now ?? Date.now;
|
|
20369
|
+
const where = locate(deps.cwd ?? process.cwd());
|
|
20370
|
+
const prior = readThrottle(where.folder);
|
|
20371
|
+
const attempt2 = await attemptObservation({
|
|
20372
|
+
repoRoot: where.repoRoot,
|
|
20373
|
+
head: where.head,
|
|
20374
|
+
config: configured.config,
|
|
20375
|
+
controlOrigin: readControlOrigin().origin,
|
|
20376
|
+
fetchFn: deps.fetchFn ?? globalThis.fetch
|
|
20377
|
+
});
|
|
20378
|
+
recordAttempt(where.folder, prior, where.head, now(), attempt2);
|
|
20379
|
+
result = attempt2.outcome === "not-sent" ? { outcome: "not-sent", reason: attempt2.reason } : { outcome: attempt2.outcome };
|
|
20380
|
+
}
|
|
20381
|
+
} catch {
|
|
20382
|
+
result = { outcome: "not-sent", reason: "unreachable" };
|
|
20383
|
+
}
|
|
20384
|
+
return observeRunResultSchema.parse(result);
|
|
20385
|
+
}
|
|
20386
|
+
async function observeChanges(sessionId, reply, deps = {}) {
|
|
20387
|
+
try {
|
|
20388
|
+
const code = process.exitCode;
|
|
20389
|
+
if (code !== void 0 && code !== null && Number(code) !== 0)
|
|
20390
|
+
return;
|
|
20391
|
+
const configured = readConfig();
|
|
20392
|
+
if (!configured.config)
|
|
20393
|
+
return;
|
|
20394
|
+
const now = (deps.now ?? Date.now)();
|
|
20395
|
+
const where = locate(deps.cwd ?? process.cwd());
|
|
20396
|
+
const prior = readThrottle(where.folder);
|
|
20397
|
+
if (!throttleAllows(prior, where.head, now))
|
|
20398
|
+
return;
|
|
20399
|
+
writeThrottle(where.folder, { attemptedAt: now, accepted: prior?.accepted ?? null });
|
|
20400
|
+
const attempt2 = await attemptObservation({
|
|
20401
|
+
repoRoot: where.repoRoot,
|
|
20402
|
+
head: where.head,
|
|
20403
|
+
config: configured.config,
|
|
20404
|
+
controlOrigin: readControlOrigin().origin,
|
|
20405
|
+
fetchFn: deps.fetchFn ?? globalThis.fetch
|
|
20406
|
+
});
|
|
20407
|
+
recordAttempt(where.folder, prior, where.head, now, attempt2);
|
|
20408
|
+
for (const line2 of noticesFor(attempt2, where.folder, sessionId))
|
|
20409
|
+
reply.notice(line2);
|
|
20410
|
+
} catch {
|
|
20411
|
+
}
|
|
20412
|
+
}
|
|
20413
|
+
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.";
|
|
20414
|
+
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.";
|
|
20415
|
+
function r3NoStartRecorded(phase) {
|
|
20416
|
+
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>"`;
|
|
20417
|
+
}
|
|
20418
|
+
function r4OutsideThePlan(parts) {
|
|
20419
|
+
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:";
|
|
20420
|
+
const lines = parts.map((entry) => {
|
|
20421
|
+
const said = entry.said.length === 0 ? NO_TASK_SAID_LINE : entry.said.map((task) => agentSaid(task)).join("; ");
|
|
20422
|
+
return `"${entry.name}": ${said}`;
|
|
20423
|
+
});
|
|
20424
|
+
return [first, ...lines, "The project's owner sees them again at the review."].join("\n");
|
|
20425
|
+
}
|
|
20426
|
+
function r5OutsideEveryPart(files, example) {
|
|
20427
|
+
if (files === 1) {
|
|
20428
|
+
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.`;
|
|
20429
|
+
}
|
|
20430
|
+
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.`;
|
|
20431
|
+
}
|
|
20432
|
+
function readSaid(path) {
|
|
20433
|
+
const empty = { parts: {}, outsideFiles: 0 };
|
|
20434
|
+
if (path === null)
|
|
20435
|
+
return empty;
|
|
20436
|
+
try {
|
|
20437
|
+
const parsed = JSON.parse(readFileSync9(path, "utf-8"));
|
|
20438
|
+
const parts = {};
|
|
20439
|
+
if (typeof parsed.parts === "object" && parsed.parts !== null) {
|
|
20440
|
+
for (const [part, reasons] of Object.entries(parsed.parts)) {
|
|
20441
|
+
if (Array.isArray(reasons))
|
|
20442
|
+
parts[part] = reasons.filter((r) => typeof r === "string");
|
|
20443
|
+
}
|
|
20444
|
+
}
|
|
20445
|
+
return { parts, outsideFiles: typeof parsed.outsideFiles === "number" ? parsed.outsideFiles : 0 };
|
|
20446
|
+
} catch {
|
|
20447
|
+
return empty;
|
|
20448
|
+
}
|
|
20449
|
+
}
|
|
20450
|
+
function writeSaid(path, state) {
|
|
20451
|
+
if (path === null)
|
|
20452
|
+
return;
|
|
20453
|
+
try {
|
|
20454
|
+
writeFileSync4(path, JSON.stringify(state), "utf-8");
|
|
20455
|
+
} catch {
|
|
20456
|
+
}
|
|
20457
|
+
}
|
|
20458
|
+
function reasonsOf(entry) {
|
|
20459
|
+
return entry.said.filter((task) => task.whyOutsidePlan !== void 0).map((task) => `${task.title}\0${task.whyOutsidePlan}`);
|
|
20460
|
+
}
|
|
20461
|
+
function noticesFor(attempt2, folder, sessionId) {
|
|
20462
|
+
if (attempt2.outcome === "not-sent") {
|
|
20463
|
+
if (attempt2.reason === "start-not-here") {
|
|
20464
|
+
return firstNoticeThisSession("observe-no-start-here", folder, sessionId) ? [R1_START_NOT_HERE] : [];
|
|
20465
|
+
}
|
|
20466
|
+
if (attempt2.reason === "start-not-on-branch") {
|
|
20467
|
+
return firstNoticeThisSession("observe-not-on-branch", folder, sessionId) ? [R2_START_NOT_ON_BRANCH] : [];
|
|
20468
|
+
}
|
|
20469
|
+
if (attempt2.reason === "cannot-count" && attempt2.phase !== void 0) {
|
|
20470
|
+
return firstNoticeThisSession("observe-no-start-recorded", folder, sessionId) ? [r3NoStartRecorded(attempt2.phase)] : [];
|
|
20471
|
+
}
|
|
20472
|
+
return [];
|
|
20473
|
+
}
|
|
20474
|
+
if (attempt2.outcome === "kept")
|
|
20475
|
+
return [];
|
|
20476
|
+
const { notInPlan, outside } = attempt2.answer;
|
|
20477
|
+
const path = observeNoticesPath(folder, sessionId);
|
|
20478
|
+
const prior = readSaid(path);
|
|
20479
|
+
const notices = [];
|
|
20480
|
+
const parts = {};
|
|
20481
|
+
let grew = false;
|
|
20482
|
+
for (const entry of notInPlan) {
|
|
20483
|
+
const reasons = reasonsOf(entry);
|
|
20484
|
+
parts[entry.part] = reasons;
|
|
20485
|
+
const before = prior.parts[entry.part];
|
|
20486
|
+
if (before === void 0 || reasons.some((reason) => !before.includes(reason)))
|
|
20487
|
+
grew = true;
|
|
20488
|
+
}
|
|
20489
|
+
if (grew)
|
|
20490
|
+
notices.push(r4OutsideThePlan(notInPlan));
|
|
20491
|
+
let outsideFiles = outside.files;
|
|
20492
|
+
if (outside.files > prior.outsideFiles) {
|
|
20493
|
+
if (outside.example !== null)
|
|
20494
|
+
notices.push(r5OutsideEveryPart(outside.files, outside.example));
|
|
20495
|
+
else
|
|
20496
|
+
outsideFiles = prior.outsideFiles;
|
|
20497
|
+
}
|
|
20498
|
+
writeSaid(path, { parts, outsideFiles });
|
|
20499
|
+
return notices;
|
|
20500
|
+
}
|
|
20501
|
+
|
|
19109
20502
|
// dist/hooks/stop.js
|
|
19110
|
-
async function runStop() {
|
|
20503
|
+
async function runStop(observeDeps = {}) {
|
|
19111
20504
|
const input = await readHookStdin();
|
|
19112
|
-
await runSessionDiff(input, "stop");
|
|
20505
|
+
await runSessionDiff(input, "stop", readSeatConfiguration, (reply) => observeChanges(input.session_id, reply, observeDeps));
|
|
19113
20506
|
}
|
|
19114
20507
|
|
|
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
20508
|
// dist/hooks/scope-phrase.js
|
|
19121
20509
|
var APPROVE_PREFIX = /^approve /i;
|
|
19122
20510
|
var SEND_BACK_PREFIX = /^send back /i;
|
|
@@ -19150,6 +20538,128 @@ function matchScopePhraseShape(shape, shown) {
|
|
|
19150
20538
|
return { kind: "show-first", act: "send-back" };
|
|
19151
20539
|
}
|
|
19152
20540
|
|
|
20541
|
+
// dist/hooks/review-phrase.js
|
|
20542
|
+
var VERBS = [
|
|
20543
|
+
[/^accept /i, "accept"],
|
|
20544
|
+
[/^question /i, "question"],
|
|
20545
|
+
[/^send back /i, "send-back"],
|
|
20546
|
+
[/^carry /i, "carry"],
|
|
20547
|
+
[/^drop /i, "drop"]
|
|
20548
|
+
];
|
|
20549
|
+
function reviewPhraseShape(prompt) {
|
|
20550
|
+
if (typeof prompt !== "string")
|
|
20551
|
+
return null;
|
|
20552
|
+
const line2 = prompt.trim();
|
|
20553
|
+
if (LINE_BREAK.test(line2))
|
|
20554
|
+
return null;
|
|
20555
|
+
for (const [prefix, act] of VERBS) {
|
|
20556
|
+
const verb = prefix.exec(line2);
|
|
20557
|
+
if (verb === null)
|
|
20558
|
+
continue;
|
|
20559
|
+
const rest = line2.slice(verb[0].length);
|
|
20560
|
+
if (act === "accept") {
|
|
20561
|
+
if (!rest.includes("/"))
|
|
20562
|
+
return isValidPhaseIdentity(rest) ? { kind: "phase", reference: rest } : null;
|
|
20563
|
+
return partShape(act, rest, null);
|
|
20564
|
+
}
|
|
20565
|
+
const colon = rest.indexOf(":");
|
|
20566
|
+
if (colon === -1)
|
|
20567
|
+
return act === "send-back" ? partShape(act, rest, null) : null;
|
|
20568
|
+
return partShape(act, rest.slice(0, colon), rest.slice(colon + 1).trim());
|
|
20569
|
+
}
|
|
20570
|
+
return null;
|
|
20571
|
+
}
|
|
20572
|
+
function partShape(act, head, words) {
|
|
20573
|
+
const slash = head.indexOf("/");
|
|
20574
|
+
if (slash === -1)
|
|
20575
|
+
return null;
|
|
20576
|
+
const reference = head.slice(0, slash);
|
|
20577
|
+
const part = head.slice(slash + 1);
|
|
20578
|
+
if (!isValidPhaseIdentity(reference) || !SYSTEM_PART_ID_PATTERN.test(part))
|
|
20579
|
+
return null;
|
|
20580
|
+
return { kind: "part", act, reference, part, words };
|
|
20581
|
+
}
|
|
20582
|
+
function matchReviewPhraseShape(shape, reviews) {
|
|
20583
|
+
const entry = resolvePhaseReference(shape.reference, reviews);
|
|
20584
|
+
if (shape.kind === "phase") {
|
|
20585
|
+
if (entry === null)
|
|
20586
|
+
return { kind: "nothing" };
|
|
20587
|
+
return {
|
|
20588
|
+
kind: "accept",
|
|
20589
|
+
entry,
|
|
20590
|
+
reference: shape.reference,
|
|
20591
|
+
parts: entry.parts.filter((p) => p.built && p.decidable)
|
|
20592
|
+
};
|
|
20593
|
+
}
|
|
20594
|
+
if (entry === null)
|
|
20595
|
+
return { kind: "show-first", act: shape.act };
|
|
20596
|
+
const part = entry.parts.find((p) => p.part === shape.part);
|
|
20597
|
+
if (part === void 0)
|
|
20598
|
+
return { kind: "not-listed", act: shape.act, entry, part: shape.part };
|
|
20599
|
+
return { kind: "decide", act: shape.act, entry, reference: shape.reference, part, words: shape.words };
|
|
20600
|
+
}
|
|
20601
|
+
|
|
20602
|
+
// dist/hooks/shown-record.js
|
|
20603
|
+
import { chmodSync as chmodSync2, readFileSync as readFileSync10, renameSync as renameSync3, unlinkSync as unlinkSync4, writeFileSync as writeFileSync5 } from "node:fs";
|
|
20604
|
+
import { homedir as homedir4, platform as platform2 } from "node:os";
|
|
20605
|
+
import { join as join8 } from "node:path";
|
|
20606
|
+
function recordDir(engagementId, home) {
|
|
20607
|
+
if (!isEngagementId(engagementId))
|
|
20608
|
+
return null;
|
|
20609
|
+
return join8(home ?? homedir4(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId);
|
|
20610
|
+
}
|
|
20611
|
+
function readShownRecord(engagementId, home) {
|
|
20612
|
+
const dir = recordDir(engagementId, home);
|
|
20613
|
+
if (dir === null)
|
|
20614
|
+
return { entries: [], reviews: [] };
|
|
20615
|
+
try {
|
|
20616
|
+
return parseShownRecord(readFileSync10(join8(dir, SHOWN_PLANS_FILENAME), "utf-8"));
|
|
20617
|
+
} catch {
|
|
20618
|
+
return { entries: [], reviews: [] };
|
|
20619
|
+
}
|
|
20620
|
+
}
|
|
20621
|
+
function movedPart(part, act) {
|
|
20622
|
+
if (act === void 0)
|
|
20623
|
+
return part;
|
|
20624
|
+
return {
|
|
20625
|
+
...part,
|
|
20626
|
+
decidable: false,
|
|
20627
|
+
awaitingAnswer: part.awaitingAnswer || act === "question",
|
|
20628
|
+
sentBack: part.sentBack || act === "send-back"
|
|
20629
|
+
};
|
|
20630
|
+
}
|
|
20631
|
+
function moveListedReviewForward(engagementId, home, listed, reviewVersion, decided) {
|
|
20632
|
+
let temp = null;
|
|
20633
|
+
try {
|
|
20634
|
+
const dir = recordDir(engagementId, home);
|
|
20635
|
+
if (dir === null)
|
|
20636
|
+
return false;
|
|
20637
|
+
const path = join8(dir, SHOWN_PLANS_FILENAME);
|
|
20638
|
+
const current = parseShownRecord(readFileSync10(path, "utf-8"));
|
|
20639
|
+
const at = current.reviews.findIndex((r) => r.phase === listed.phase && r.shownAt === listed.shownAt && r.reviewVersion === listed.reviewVersion);
|
|
20640
|
+
if (at === -1)
|
|
20641
|
+
return false;
|
|
20642
|
+
const acts = new Map(decided.map((d) => [d.part, d.act]));
|
|
20643
|
+
const reviews = current.reviews.map((review, i) => i === at ? { ...review, reviewVersion, parts: review.parts.map((p) => movedPart(p, acts.get(p.part))) } : review);
|
|
20644
|
+
const body = serialiseShownRecord({ entries: current.entries, reviews });
|
|
20645
|
+
temp = join8(dir, `.${SHOWN_PLANS_FILENAME}.${process.pid}.tmp`);
|
|
20646
|
+
writeFileSync5(temp, body, { mode: 384 });
|
|
20647
|
+
if (platform2() !== "win32")
|
|
20648
|
+
chmodSync2(temp, 384);
|
|
20649
|
+
renameSync3(temp, path);
|
|
20650
|
+
return true;
|
|
20651
|
+
} catch {
|
|
20652
|
+
if (temp !== null) {
|
|
20653
|
+
try {
|
|
20654
|
+
unlinkSync4(temp);
|
|
20655
|
+
} catch {
|
|
20656
|
+
return false;
|
|
20657
|
+
}
|
|
20658
|
+
}
|
|
20659
|
+
return false;
|
|
20660
|
+
}
|
|
20661
|
+
}
|
|
20662
|
+
|
|
19153
20663
|
// dist/hooks/user-prompt-submit-lines.js
|
|
19154
20664
|
var TELL_THE_PERSON = "Tell the person.";
|
|
19155
20665
|
function line(text) {
|
|
@@ -19225,14 +20735,210 @@ function lineForAnswer(act, phase, answer) {
|
|
|
19225
20735
|
}
|
|
19226
20736
|
}
|
|
19227
20737
|
}
|
|
20738
|
+
var NOTHING_RECORDED = "Nothing was recorded";
|
|
20739
|
+
var LIST_AGAIN = 'Say "review what was built" to list it again';
|
|
20740
|
+
function reviewNotAttendedLine() {
|
|
20741
|
+
return line(`${NOTHING_RECORDED}: reviewing by typing counts only when a person types it into an open session.`);
|
|
20742
|
+
}
|
|
20743
|
+
function reviewNotSignedInLine() {
|
|
20744
|
+
return line(`${NOTHING_RECORDED}: this project is not signed in on this computer. ${HOOK_REMEDY}`);
|
|
20745
|
+
}
|
|
20746
|
+
function reviewCannotReachLine() {
|
|
20747
|
+
return line(`${NOTHING_RECORDED}: Halfcycle could not be reached just now. Type the line again in a minute.`);
|
|
20748
|
+
}
|
|
20749
|
+
function reviewSwitchedOffLine() {
|
|
20750
|
+
return line(`${NOTHING_RECORDED}. ${TYPED_REVIEW_OFF_SENTENCE}`);
|
|
20751
|
+
}
|
|
20752
|
+
function reviewNotOwnerLine() {
|
|
20753
|
+
return line(`${NOTHING_RECORDED}: only the project's owner can review this phase.`);
|
|
20754
|
+
}
|
|
20755
|
+
function reviewNoOwnerLine() {
|
|
20756
|
+
return line(`${NOTHING_RECORDED}: this project has no owner, so nobody can review its phases.`);
|
|
20757
|
+
}
|
|
20758
|
+
function reviewWrongActLine(name, built) {
|
|
20759
|
+
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.`);
|
|
20760
|
+
}
|
|
20761
|
+
function wordsActNoun(act) {
|
|
20762
|
+
switch (act) {
|
|
20763
|
+
case "question":
|
|
20764
|
+
return "a question";
|
|
20765
|
+
case "send-back":
|
|
20766
|
+
return "a send-back";
|
|
20767
|
+
case "carry":
|
|
20768
|
+
return "a carry";
|
|
20769
|
+
case "drop":
|
|
20770
|
+
return "a drop";
|
|
20771
|
+
default: {
|
|
20772
|
+
const unreachable = act;
|
|
20773
|
+
return unreachable;
|
|
20774
|
+
}
|
|
20775
|
+
}
|
|
20776
|
+
}
|
|
20777
|
+
function reviewNoWordsLine(act) {
|
|
20778
|
+
return line(`${NOTHING_RECORDED}: ${wordsActNoun(act)} needs words after the colon.`);
|
|
20779
|
+
}
|
|
20780
|
+
function reviewWordsTooLongLine() {
|
|
20781
|
+
return line(`${NOTHING_RECORDED}: the words after the colon are too long. Shorten them and type the line again.`);
|
|
20782
|
+
}
|
|
20783
|
+
function reviewWordsLookSecretLine() {
|
|
20784
|
+
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.`);
|
|
20785
|
+
}
|
|
20786
|
+
function reviewShowFirstLine() {
|
|
20787
|
+
return line(`${NOTHING_RECORDED}: that review has not been listed here. Say "review what was built" to list it, then type the line again.`);
|
|
20788
|
+
}
|
|
20789
|
+
function reviewPartNotListedLine(part, phase) {
|
|
20790
|
+
return line(`${NOTHING_RECORDED}: "${part}" is not a part of the review of ${phase} as it was listed here.`);
|
|
20791
|
+
}
|
|
20792
|
+
function reviewNothingToAcceptLine(phase) {
|
|
20793
|
+
return line(`${NOTHING_RECORDED}: no built part of ${phase} waits for your decision.`);
|
|
20794
|
+
}
|
|
20795
|
+
function reviewListedTooLongAgoLine(phase) {
|
|
20796
|
+
return line(`${NOTHING_RECORDED}: ${phase} was listed an hour or more ago. ${LIST_AGAIN}, then type the line again within the hour.`);
|
|
20797
|
+
}
|
|
20798
|
+
function reviewChangedLine(phase) {
|
|
20799
|
+
return line(`${NOTHING_RECORDED}: the review of ${phase} changed after it was listed here. ${LIST_AGAIN}, then type the line again.`);
|
|
20800
|
+
}
|
|
20801
|
+
function reviewAlreadyDecidedLine(name) {
|
|
20802
|
+
return line(`${NOTHING_RECORDED}: "${name}" is already decided in this review.`);
|
|
20803
|
+
}
|
|
20804
|
+
function reviewAwaitingAnswerLine(name) {
|
|
20805
|
+
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.`);
|
|
20806
|
+
}
|
|
20807
|
+
function reviewSentBackLine(name) {
|
|
20808
|
+
return line(`${NOTHING_RECORDED}: "${name}" was sent back to building in this round, so it waits for the next round.`);
|
|
20809
|
+
}
|
|
20810
|
+
function reviewWalkChangedLine(phase, number4) {
|
|
20811
|
+
const typed = typedPhaseReference(phase, number4);
|
|
20812
|
+
return line(`${NOTHING_RECORDED}: phase ${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.`);
|
|
20813
|
+
}
|
|
20814
|
+
function reviewNoReviewLine(phase) {
|
|
20815
|
+
return line(`${NOTHING_RECORDED}: phase ${phase} has no review open.`);
|
|
20816
|
+
}
|
|
20817
|
+
function reviewCloseRefusedLine(phase, steps) {
|
|
20818
|
+
if (steps.length === 0) {
|
|
20819
|
+
return line(`${NOTHING_RECORDED}: phase ${phase} could not close just now. Type the line again in a minute.`);
|
|
20820
|
+
}
|
|
20821
|
+
const labels = steps.map(unfinishedStepLabel).join(", ");
|
|
20822
|
+
const head = steps.length === 1 ? `${NOTHING_RECORDED}: phase ${phase} cannot close while 1 of its steps is unfinished: ${labels}.` : `${NOTHING_RECORDED}: phase ${phase} cannot close while ${steps.length} of its steps are unfinished: ${labels}.`;
|
|
20823
|
+
return line([head, ...steps.map(unfinishedStepLine), "Then type the line again."].join(" "));
|
|
20824
|
+
}
|
|
20825
|
+
function reviewRefusedLine(act) {
|
|
20826
|
+
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.`);
|
|
20827
|
+
}
|
|
20828
|
+
function stillWaitingOf(phase, waiting) {
|
|
20829
|
+
if (waiting === 0)
|
|
20830
|
+
return `No part of ${phase} waits for your decision now.`;
|
|
20831
|
+
if (waiting === 1)
|
|
20832
|
+
return `1 part of ${phase} still waits for your decision.`;
|
|
20833
|
+
return `${waiting} parts of ${phase} still wait for your decision.`;
|
|
20834
|
+
}
|
|
20835
|
+
function stillWaiting(waiting) {
|
|
20836
|
+
if (waiting === 0)
|
|
20837
|
+
return "No part waits for your decision now.";
|
|
20838
|
+
if (waiting === 1)
|
|
20839
|
+
return "1 part still waits for your decision.";
|
|
20840
|
+
return `${waiting} parts still wait for your decision.`;
|
|
20841
|
+
}
|
|
20842
|
+
function decidedWords(decided) {
|
|
20843
|
+
switch (decided.act) {
|
|
20844
|
+
case "accept":
|
|
20845
|
+
return "accepted";
|
|
20846
|
+
case "question":
|
|
20847
|
+
return "questioned";
|
|
20848
|
+
case "send-back":
|
|
20849
|
+
return "sent back to building";
|
|
20850
|
+
case "carry":
|
|
20851
|
+
return decided.carriedTo === void 0 || decided.carriedTo === null ? "carried to the next phase planned" : `carried to phase ${decided.carriedTo.number}, "${decided.carriedTo.phase}"`;
|
|
20852
|
+
case "drop":
|
|
20853
|
+
return "dropped";
|
|
20854
|
+
default: {
|
|
20855
|
+
const unreachable = decided.act;
|
|
20856
|
+
return unreachable;
|
|
20857
|
+
}
|
|
20858
|
+
}
|
|
20859
|
+
}
|
|
20860
|
+
function reviewRecordedLine(decided, phase, waiting) {
|
|
20861
|
+
return line(`Recorded in your session: "${decided.name}" ${decidedWords(decided)}. ${stillWaitingOf(phase, waiting)}`);
|
|
20862
|
+
}
|
|
20863
|
+
function reviewBulkRecordedLine(accepted, phase, waiting) {
|
|
20864
|
+
const first = accepted === 1 ? `Recorded in your session: 1 part of ${phase} accepted.` : `Recorded in your session: ${accepted} parts of ${phase} accepted.`;
|
|
20865
|
+
return line(`${first} ${stillWaiting(waiting)}`);
|
|
20866
|
+
}
|
|
20867
|
+
function reviewClosedLine(phase) {
|
|
20868
|
+
return line(`Recorded in your session. That was the last decision: phase ${phase} is closed, and its Build Record is ready.`);
|
|
20869
|
+
}
|
|
20870
|
+
function reviewNoPartsClosedLine(phase) {
|
|
20871
|
+
return line(`Recorded in your session. Phase ${phase} had no parts to review, so it is closed, and its Build Record is ready.`);
|
|
20872
|
+
}
|
|
20873
|
+
function notDecidableLine(answer) {
|
|
20874
|
+
const first = answer.parts[0];
|
|
20875
|
+
switch (first.why) {
|
|
20876
|
+
case "question-waiting":
|
|
20877
|
+
return reviewAwaitingAnswerLine(first.name);
|
|
20878
|
+
case "sent-back":
|
|
20879
|
+
return reviewSentBackLine(first.name);
|
|
20880
|
+
case "settled":
|
|
20881
|
+
return reviewAlreadyDecidedLine(first.name);
|
|
20882
|
+
default: {
|
|
20883
|
+
const unreachable = first.why;
|
|
20884
|
+
return unreachable;
|
|
20885
|
+
}
|
|
20886
|
+
}
|
|
20887
|
+
}
|
|
20888
|
+
function reviewLineForAnswer(context, answer) {
|
|
20889
|
+
const { phase } = context;
|
|
20890
|
+
switch (answer.outcome) {
|
|
20891
|
+
case "recorded": {
|
|
20892
|
+
if (answer.closed) {
|
|
20893
|
+
return answer.decided.length === 0 ? reviewNoPartsClosedLine(phase) : reviewClosedLine(phase);
|
|
20894
|
+
}
|
|
20895
|
+
if (context.bulk)
|
|
20896
|
+
return reviewBulkRecordedLine(answer.decided.length, phase, answer.waiting);
|
|
20897
|
+
return reviewRecordedLine(answer.decided[0], phase, answer.waiting);
|
|
20898
|
+
}
|
|
20899
|
+
case "review-changed":
|
|
20900
|
+
return reviewChangedLine(phase);
|
|
20901
|
+
case "no-review":
|
|
20902
|
+
return reviewNoReviewLine(phase);
|
|
20903
|
+
case "not-decidable":
|
|
20904
|
+
return notDecidableLine(answer);
|
|
20905
|
+
case "close-refused":
|
|
20906
|
+
return reviewCloseRefusedLine(phase, answer.steps);
|
|
20907
|
+
case "walk-changed":
|
|
20908
|
+
return reviewWalkChangedLine(phase, context.number);
|
|
20909
|
+
case "not-owner":
|
|
20910
|
+
return reviewNotOwnerLine();
|
|
20911
|
+
case "no-owner":
|
|
20912
|
+
return reviewNoOwnerLine();
|
|
20913
|
+
case "refused":
|
|
20914
|
+
return reviewRefusedLine(context.act);
|
|
20915
|
+
case "unavailable":
|
|
20916
|
+
return reviewCannotReachLine();
|
|
20917
|
+
case "show-first":
|
|
20918
|
+
return reviewShowFirstLine();
|
|
20919
|
+
case "not-offered":
|
|
20920
|
+
return reviewSwitchedOffLine();
|
|
20921
|
+
default: {
|
|
20922
|
+
const unreachable = answer;
|
|
20923
|
+
return unreachable;
|
|
20924
|
+
}
|
|
20925
|
+
}
|
|
20926
|
+
}
|
|
19228
20927
|
|
|
19229
20928
|
// dist/hooks/user-prompt-submit.js
|
|
19230
20929
|
var CHAT_REQUEST_TIMEOUT_MS = 6e3;
|
|
19231
20930
|
var SHOWN_ENTRY_MAX_AGE_MS = 60 * 60 * 1e3;
|
|
19232
20931
|
async function runUserPromptSubmit(opts) {
|
|
19233
20932
|
let act = null;
|
|
20933
|
+
const review = { recognised: false };
|
|
19234
20934
|
try {
|
|
19235
20935
|
const prompt = promptOf(opts.input);
|
|
20936
|
+
const reviewShape = reviewPhraseShape(prompt);
|
|
20937
|
+
if (reviewShape !== null) {
|
|
20938
|
+
const out = await decideReview(reviewShape, opts, review);
|
|
20939
|
+
return out === "" ? "" : `${out}
|
|
20940
|
+
`;
|
|
20941
|
+
}
|
|
19236
20942
|
const shape = scopePhraseShape(prompt);
|
|
19237
20943
|
if (shape === null)
|
|
19238
20944
|
return "";
|
|
@@ -19240,10 +20946,114 @@ async function runUserPromptSubmit(opts) {
|
|
|
19240
20946
|
return `${await decide(shape, opts)}
|
|
19241
20947
|
`;
|
|
19242
20948
|
} catch {
|
|
20949
|
+
if (review.recognised)
|
|
20950
|
+
return `${reviewCannotReachLine()}
|
|
20951
|
+
`;
|
|
19243
20952
|
return act === null ? "" : `${cannotReachLine(act)}
|
|
19244
20953
|
`;
|
|
19245
20954
|
}
|
|
19246
20955
|
}
|
|
20956
|
+
async function decideReview(shape, opts, progress) {
|
|
20957
|
+
const env = opts.env ?? process.env;
|
|
20958
|
+
if (shape.kind === "phase") {
|
|
20959
|
+
const read2 = readConfig(env);
|
|
20960
|
+
if (read2.config === null)
|
|
20961
|
+
return "";
|
|
20962
|
+
const match2 = matchReviewPhraseShape(shape, readShownRecord(read2.config.guardEngagementId, opts.home).reviews);
|
|
20963
|
+
if (match2.kind === "nothing")
|
|
20964
|
+
return "";
|
|
20965
|
+
progress.recognised = true;
|
|
20966
|
+
if (!readSessionAttended(env))
|
|
20967
|
+
return reviewNotAttendedLine();
|
|
20968
|
+
return checkAndSendReview(match2, read2.config.guardEngagementId, read2.config.guardServiceToken, env, opts);
|
|
20969
|
+
}
|
|
20970
|
+
progress.recognised = true;
|
|
20971
|
+
if (!readSessionAttended(env))
|
|
20972
|
+
return reviewNotAttendedLine();
|
|
20973
|
+
const read = readConfig(env);
|
|
20974
|
+
if (read.config === null) {
|
|
20975
|
+
return readNeverConfiguredReason(env) === "not-this-account" ? reviewNotOwnerLine() : reviewNotSignedInLine();
|
|
20976
|
+
}
|
|
20977
|
+
const match = matchReviewPhraseShape(shape, readShownRecord(read.config.guardEngagementId, opts.home).reviews);
|
|
20978
|
+
return checkAndSendReview(match, read.config.guardEngagementId, read.config.guardServiceToken, env, opts);
|
|
20979
|
+
}
|
|
20980
|
+
async function checkAndSendReview(match, engagementId, credential, env, opts) {
|
|
20981
|
+
if (match.kind === "nothing" || match.kind === "show-first")
|
|
20982
|
+
return reviewShowFirstLine();
|
|
20983
|
+
if (match.kind === "not-listed")
|
|
20984
|
+
return reviewPartNotListedLine(match.part, match.entry.phase);
|
|
20985
|
+
const { entry } = match;
|
|
20986
|
+
if (!listedRecently(entry.shownAt, (opts.now ?? Date.now)()))
|
|
20987
|
+
return reviewListedTooLongAgoLine(entry.phase);
|
|
20988
|
+
let candidate;
|
|
20989
|
+
if (match.kind === "accept") {
|
|
20990
|
+
if (entry.parts.length > 0 && match.parts.length === 0)
|
|
20991
|
+
return reviewNothingToAcceptLine(entry.phase);
|
|
20992
|
+
candidate = {
|
|
20993
|
+
act: "accept",
|
|
20994
|
+
reference: match.reference,
|
|
20995
|
+
reviewVersion: entry.reviewVersion,
|
|
20996
|
+
parts: match.parts.map((p) => p.part)
|
|
20997
|
+
};
|
|
20998
|
+
} else {
|
|
20999
|
+
const { act, part } = match;
|
|
21000
|
+
if (!reviewActsFor(part.built).includes(act))
|
|
21001
|
+
return reviewWrongActLine(part.name, part.built);
|
|
21002
|
+
let words = "";
|
|
21003
|
+
if (act !== "accept") {
|
|
21004
|
+
words = match.words ?? "";
|
|
21005
|
+
if (words === "")
|
|
21006
|
+
return reviewNoWordsLine(act);
|
|
21007
|
+
if (!reviewNoteWireSchema.safeParse(words).success)
|
|
21008
|
+
return reviewWordsTooLongLine();
|
|
21009
|
+
if (looksLikeSecret(words))
|
|
21010
|
+
return reviewWordsLookSecretLine();
|
|
21011
|
+
}
|
|
21012
|
+
if (part.awaitingAnswer)
|
|
21013
|
+
return reviewAwaitingAnswerLine(part.name);
|
|
21014
|
+
if (part.sentBack)
|
|
21015
|
+
return reviewSentBackLine(part.name);
|
|
21016
|
+
if (!part.decidable)
|
|
21017
|
+
return reviewAlreadyDecidedLine(part.name);
|
|
21018
|
+
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 };
|
|
21019
|
+
}
|
|
21020
|
+
const request = chatReviewRequestSchema.safeParse(candidate);
|
|
21021
|
+
if (!request.success)
|
|
21022
|
+
return reviewShowFirstLine();
|
|
21023
|
+
return postReview(match.kind === "accept", entry, request.data, engagementId, credential, env, opts);
|
|
21024
|
+
}
|
|
21025
|
+
async function postReview(bulk, entry, request, engagementId, credential, env, opts) {
|
|
21026
|
+
const fetchFn = opts.fetchFn ?? globalThis.fetch;
|
|
21027
|
+
const url2 = `${readControlOrigin(env).origin}/engagements/${encodeURIComponent(engagementId)}/phases/${encodeURIComponent(entry.phase)}/review/chat`;
|
|
21028
|
+
const controller = new AbortController();
|
|
21029
|
+
const timer = setTimeout(() => controller.abort(), CHAT_REQUEST_TIMEOUT_MS);
|
|
21030
|
+
try {
|
|
21031
|
+
const response = await fetchFn(url2, {
|
|
21032
|
+
method: "POST",
|
|
21033
|
+
headers: { Authorization: `Bearer ${credential}`, "Content-Type": "application/json" },
|
|
21034
|
+
body: JSON.stringify(request),
|
|
21035
|
+
signal: controller.signal
|
|
21036
|
+
});
|
|
21037
|
+
if (response.status === 401)
|
|
21038
|
+
return reviewNotSignedInLine();
|
|
21039
|
+
if (response.status === 429 || response.status >= 500)
|
|
21040
|
+
return reviewCannotReachLine();
|
|
21041
|
+
const parsed = chatReviewResultSchema.safeParse(await response.json());
|
|
21042
|
+
if (!parsed.success)
|
|
21043
|
+
return reviewCannotReachLine();
|
|
21044
|
+
const answer = parsed.data;
|
|
21045
|
+
if (answer.outcome === "recorded") {
|
|
21046
|
+
if (response.status !== 200)
|
|
21047
|
+
return reviewCannotReachLine();
|
|
21048
|
+
moveListedReviewForward(engagementId, opts.home, entry, answer.reviewVersion, answer.decided);
|
|
21049
|
+
}
|
|
21050
|
+
return reviewLineForAnswer({ act: request.act, bulk, phase: entry.phase, number: entry.number }, answer);
|
|
21051
|
+
} catch {
|
|
21052
|
+
return reviewCannotReachLine();
|
|
21053
|
+
} finally {
|
|
21054
|
+
clearTimeout(timer);
|
|
21055
|
+
}
|
|
21056
|
+
}
|
|
19247
21057
|
async function decide(shape, opts) {
|
|
19248
21058
|
const { act } = shape;
|
|
19249
21059
|
const env = opts.env ?? process.env;
|
|
@@ -19254,12 +21064,12 @@ async function decide(shape, opts) {
|
|
|
19254
21064
|
return readNeverConfiguredReason(env) === "not-this-account" ? notOwnerLine(act) : notSignedInLine(act);
|
|
19255
21065
|
}
|
|
19256
21066
|
const { guardServiceToken, guardEngagementId } = read.config;
|
|
19257
|
-
const shown =
|
|
21067
|
+
const shown = readShownRecord(guardEngagementId, opts.home).entries;
|
|
19258
21068
|
const match = matchScopePhraseShape(shape, shown);
|
|
19259
21069
|
if (match.kind === "show-first")
|
|
19260
21070
|
return showFirstLine(act);
|
|
19261
21071
|
const { entry } = match;
|
|
19262
|
-
if (!
|
|
21072
|
+
if (!listedRecently(entry.shownAt, (opts.now ?? Date.now)()))
|
|
19263
21073
|
return tooOldLine(act, entry.phase);
|
|
19264
21074
|
let candidate;
|
|
19265
21075
|
if (match.kind === "send-back") {
|
|
@@ -19288,19 +21098,8 @@ function promptOf(input) {
|
|
|
19288
21098
|
return void 0;
|
|
19289
21099
|
}
|
|
19290
21100
|
}
|
|
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);
|
|
21101
|
+
function listedRecently(shownAt, now) {
|
|
21102
|
+
const age = now - Date.parse(shownAt);
|
|
19304
21103
|
return Number.isFinite(age) && age >= 0 && age < SHOWN_ENTRY_MAX_AGE_MS;
|
|
19305
21104
|
}
|
|
19306
21105
|
async function post(act, entry, request, engagementId, credential, env, fetchFn = globalThis.fetch) {
|
|
@@ -19332,7 +21131,7 @@ async function post(act, entry, request, engagementId, credential, env, fetchFn
|
|
|
19332
21131
|
}
|
|
19333
21132
|
|
|
19334
21133
|
// dist/resolve-engagement.js
|
|
19335
|
-
import { existsSync as existsSync6, readFileSync as
|
|
21134
|
+
import { existsSync as existsSync6, readFileSync as readFileSync11, realpathSync as realpathSync3 } from "node:fs";
|
|
19336
21135
|
import { join as join9, sep as sep2 } from "node:path";
|
|
19337
21136
|
import { homedir as homedir5 } from "node:os";
|
|
19338
21137
|
function defaultRegistryPath() {
|
|
@@ -19342,7 +21141,7 @@ function readRegistry(registryPath = defaultRegistryPath()) {
|
|
|
19342
21141
|
if (!existsSync6(registryPath))
|
|
19343
21142
|
return {};
|
|
19344
21143
|
try {
|
|
19345
|
-
const parsed = JSON.parse(
|
|
21144
|
+
const parsed = JSON.parse(readFileSync11(registryPath, "utf-8"));
|
|
19346
21145
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
19347
21146
|
return {};
|
|
19348
21147
|
const out = {};
|
|
@@ -19390,11 +21189,11 @@ function runResolveEngagement(cwd, registryPath) {
|
|
|
19390
21189
|
}
|
|
19391
21190
|
|
|
19392
21191
|
// dist/refresh-credential.js
|
|
19393
|
-
import { readFileSync as
|
|
21192
|
+
import { readFileSync as readFileSync13 } from "node:fs";
|
|
19394
21193
|
import { resolve as resolve4 } from "node:path";
|
|
19395
21194
|
|
|
19396
21195
|
// dist/mcp-headers.js
|
|
19397
|
-
import { readFileSync as
|
|
21196
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
19398
21197
|
import { homedir as homedir6 } from "node:os";
|
|
19399
21198
|
import { dirname as dirname4, resolve as resolve3 } from "node:path";
|
|
19400
21199
|
function defaultWriteErr(text) {
|
|
@@ -19414,7 +21213,7 @@ function projectRootFromBinary(binaryPath) {
|
|
|
19414
21213
|
function readPinnedVersion(root) {
|
|
19415
21214
|
let raw;
|
|
19416
21215
|
try {
|
|
19417
|
-
raw =
|
|
21216
|
+
raw = readFileSync12(resolve3(root, BUNDLE_PIN_REL), "utf-8");
|
|
19418
21217
|
} catch {
|
|
19419
21218
|
return void 0;
|
|
19420
21219
|
}
|
|
@@ -19640,7 +21439,7 @@ var INITIALIZE_BODY = JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initializ
|
|
|
19640
21439
|
function registeredMcpUrl(projectRoot) {
|
|
19641
21440
|
let parsed;
|
|
19642
21441
|
try {
|
|
19643
|
-
parsed = JSON.parse(
|
|
21442
|
+
parsed = JSON.parse(readFileSync13(resolve4(projectRoot, MCP_REGISTRATION_REL), "utf-8"));
|
|
19644
21443
|
} catch {
|
|
19645
21444
|
return void 0;
|
|
19646
21445
|
}
|
|
@@ -19747,7 +21546,7 @@ function positionRequestBody(engagementId) {
|
|
|
19747
21546
|
params: { name: "halfcycle_position", arguments: { engagementId } }
|
|
19748
21547
|
});
|
|
19749
21548
|
}
|
|
19750
|
-
async function
|
|
21549
|
+
async function fetchPositionNotices(url2, bearer, engagementId, fetchFn, deadlineMs) {
|
|
19751
21550
|
const controller = new AbortController();
|
|
19752
21551
|
const timer = setTimeout(() => controller.abort(), deadlineMs);
|
|
19753
21552
|
try {
|
|
@@ -19794,9 +21593,11 @@ async function fetchScopeNotices(url2, bearer, engagementId, fetchFn, deadlineMs
|
|
|
19794
21593
|
} catch {
|
|
19795
21594
|
return void 0;
|
|
19796
21595
|
}
|
|
19797
|
-
|
|
19798
|
-
|
|
19799
|
-
|
|
21596
|
+
if (typeof body !== "object" || body === null)
|
|
21597
|
+
return void 0;
|
|
21598
|
+
const scopeParsed = scopeNoticesSchema.safeParse(body["scope"]);
|
|
21599
|
+
const reviewParsed = reviewNoticesSchema.safeParse(body["review"]);
|
|
21600
|
+
return { scope: scopeParsed.success ? scopeParsed.data : void 0, review: reviewParsed.success ? reviewParsed.data : void 0 };
|
|
19800
21601
|
} finally {
|
|
19801
21602
|
clearTimeout(timer);
|
|
19802
21603
|
}
|
|
@@ -19833,9 +21634,11 @@ async function runRefreshCredential(opts) {
|
|
|
19833
21634
|
const remainingForMargin = SESSION_START_HOOK_TIMEOUT_MS - SESSION_START_MARGIN_MS - (Date.now() - startedAt);
|
|
19834
21635
|
const positionDeadline = Math.min(opts.scopeNoticeDeadlineMs ?? SCOPE_NOTICE_DEADLINE_MS, remainingForMargin);
|
|
19835
21636
|
if (positionDeadline > 0) {
|
|
19836
|
-
const
|
|
19837
|
-
if (scope !== void 0)
|
|
19838
|
-
parts.push(...scopeNoticeLines(scope));
|
|
21637
|
+
const notices = await fetchPositionNotices(connection.url, connection.bearer, engagementId, opts.fetchFn ?? globalThis.fetch, positionDeadline);
|
|
21638
|
+
if (notices?.scope !== void 0)
|
|
21639
|
+
parts.push(...scopeNoticeLines(notices.scope));
|
|
21640
|
+
if (notices?.review !== void 0)
|
|
21641
|
+
parts.push(...reviewNoticeText(notices.review));
|
|
19839
21642
|
}
|
|
19840
21643
|
}
|
|
19841
21644
|
} else if (connection.sentence === CANNOT_REACH_SENTENCE || connection.sentence === SERVICE_PROBLEM_SENTENCE) {
|
|
@@ -19869,7 +21672,7 @@ async function runRefreshCredential(opts) {
|
|
|
19869
21672
|
import { fileURLToPath } from "node:url";
|
|
19870
21673
|
|
|
19871
21674
|
// dist/capture.js
|
|
19872
|
-
import { readFileSync as
|
|
21675
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync6, mkdirSync as mkdirSync3, existsSync as existsSync7 } from "node:fs";
|
|
19873
21676
|
import { dirname as dirname5 } from "node:path";
|
|
19874
21677
|
function runCapture(provider, endpoint, opts = {}) {
|
|
19875
21678
|
const repoRoot = opts.repoRoot ?? process.cwd();
|
|
@@ -19906,7 +21709,7 @@ function runCapture(provider, endpoint, opts = {}) {
|
|
|
19906
21709
|
const manifestFile = manifestPath(repoRoot);
|
|
19907
21710
|
try {
|
|
19908
21711
|
mkdirSync3(dirname5(fixture), { recursive: true });
|
|
19909
|
-
|
|
21712
|
+
writeFileSync6(fixture, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
19910
21713
|
} catch (err) {
|
|
19911
21714
|
const msg = err instanceof Error ? err.message : String(err);
|
|
19912
21715
|
return { ok: false, kind: "write-failed", message: `could not write fixture: ${msg}` };
|
|
@@ -19937,7 +21740,7 @@ function readInputFile(fromFile) {
|
|
|
19937
21740
|
if (!fromFile)
|
|
19938
21741
|
return void 0;
|
|
19939
21742
|
try {
|
|
19940
|
-
return
|
|
21743
|
+
return readFileSync14(fromFile, "utf-8");
|
|
19941
21744
|
} catch {
|
|
19942
21745
|
return void 0;
|
|
19943
21746
|
}
|
|
@@ -19961,6 +21764,17 @@ async function main() {
|
|
|
19961
21764
|
process.stdout.write(reply, () => process.exit(0));
|
|
19962
21765
|
return;
|
|
19963
21766
|
}
|
|
21767
|
+
if (cmd === "observe") {
|
|
21768
|
+
let line2;
|
|
21769
|
+
try {
|
|
21770
|
+
line2 = JSON.stringify(await runObserve());
|
|
21771
|
+
} catch {
|
|
21772
|
+
line2 = JSON.stringify({ outcome: "not-sent", reason: "unreachable" });
|
|
21773
|
+
}
|
|
21774
|
+
process.stdout.write(`${line2}
|
|
21775
|
+
`, () => process.exit(0));
|
|
21776
|
+
return;
|
|
21777
|
+
}
|
|
19964
21778
|
if (cmd === "mcp-headers") {
|
|
19965
21779
|
const line2 = await mcpHeaders({ binaryPath: fileURLToPath(import.meta.url) });
|
|
19966
21780
|
process.stdout.write(line2, () => process.exit(0));
|
|
@@ -20043,6 +21857,7 @@ Commit the fixture; the next guard evaluation of this endpoint will pass.
|
|
|
20043
21857
|
halfcycle-runner hook user-prompt-submit
|
|
20044
21858
|
halfcycle-runner refresh-credential
|
|
20045
21859
|
halfcycle-runner mcp-headers
|
|
21860
|
+
halfcycle-runner observe
|
|
20046
21861
|
halfcycle-runner resolve-engagement <cwd>
|
|
20047
21862
|
halfcycle-runner capture <provider> <endpoint> [--from <file>]
|
|
20048
21863
|
`);
|