@gajae-code/coding-agent 0.4.5 → 0.5.0
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/CHANGELOG.md +43 -0
- package/dist/types/commands/harness.d.ts +3 -0
- package/dist/types/config/model-profile-activation.d.ts +11 -2
- package/dist/types/config/model-profiles.d.ts +7 -0
- package/dist/types/config/model-registry.d.ts +3 -0
- package/dist/types/config/model-resolver.d.ts +2 -0
- package/dist/types/config/models-config-schema.d.ts +30 -0
- package/dist/types/config/settings-schema.d.ts +4 -3
- package/dist/types/gjc-runtime/team-runtime.d.ts +0 -1
- package/dist/types/gjc-runtime/tmux-common.d.ts +3 -0
- package/dist/types/harness-control-plane/owner.d.ts +1 -1
- package/dist/types/harness-control-plane/receipt-spool.d.ts +19 -0
- package/dist/types/harness-control-plane/state-machine.d.ts +6 -1
- package/dist/types/harness-control-plane/types.d.ts +4 -0
- package/dist/types/hindsight/mental-models.d.ts +5 -5
- package/dist/types/modes/components/model-selector.d.ts +1 -12
- package/dist/types/modes/rpc/rpc-client.d.ts +2 -2
- package/dist/types/modes/rpc/rpc-types.d.ts +4 -1
- package/dist/types/sdk.d.ts +5 -0
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/session/blob-store.d.ts +20 -1
- package/dist/types/session/session-manager.d.ts +24 -6
- package/dist/types/session/streaming-output.d.ts +3 -2
- package/dist/types/session/tool-choice-queue.d.ts +6 -0
- package/dist/types/task/receipt.d.ts +1 -0
- package/dist/types/task/types.d.ts +7 -0
- package/dist/types/thinking-metadata.d.ts +16 -0
- package/dist/types/thinking.d.ts +3 -12
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/resolve.d.ts +0 -10
- package/dist/types/utils/tool-choice.d.ts +14 -1
- package/package.json +7 -7
- package/src/cli.ts +8 -4
- package/src/commands/harness.ts +36 -2
- package/src/commands/launch.ts +2 -2
- package/src/commands/session.ts +3 -1
- package/src/config/model-profile-activation.ts +15 -3
- package/src/config/model-profiles.ts +255 -56
- package/src/config/model-resolver.ts +9 -6
- package/src/config/models-config-schema.ts +1 -0
- package/src/config/settings-schema.ts +6 -3
- package/src/coordinator-mcp/server.ts +54 -23
- package/src/cursor.ts +16 -2
- package/src/defaults/gjc/skills/team/SKILL.md +3 -2
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +8 -2
- package/src/export/html/index.ts +13 -9
- package/src/gjc-runtime/team-runtime.ts +33 -7
- package/src/gjc-runtime/tmux-common.ts +15 -0
- package/src/gjc-runtime/tmux-sessions.ts +19 -11
- package/src/gjc-runtime/ultragoal-runtime.ts +505 -41
- package/src/gjc-runtime/workflow-manifest.generated.json +27 -1
- package/src/gjc-runtime/workflow-manifest.ts +16 -1
- package/src/harness-control-plane/owner.ts +78 -27
- package/src/harness-control-plane/receipt-spool.ts +128 -0
- package/src/harness-control-plane/state-machine.ts +27 -6
- package/src/harness-control-plane/storage.ts +23 -0
- package/src/harness-control-plane/types.ts +4 -0
- package/src/hindsight/mental-models.ts +17 -16
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/modes/components/assistant-message.ts +26 -14
- package/src/modes/components/diff.ts +97 -0
- package/src/modes/components/model-selector.ts +353 -181
- package/src/modes/components/tool-execution.ts +30 -13
- package/src/modes/controllers/selector-controller.ts +33 -42
- package/src/modes/rpc/rpc-client.ts +3 -2
- package/src/modes/rpc/rpc-mode.ts +44 -14
- package/src/modes/rpc/rpc-types.ts +5 -2
- package/src/modes/shared/agent-wire/command-dispatch.ts +10 -5
- package/src/modes/shared/agent-wire/command-validation.ts +11 -0
- package/src/sdk.ts +29 -2
- package/src/secrets/obfuscator.ts +102 -27
- package/src/session/agent-session.ts +105 -20
- package/src/session/blob-store.ts +89 -3
- package/src/session/session-manager.ts +309 -58
- package/src/session/streaming-output.ts +185 -122
- package/src/session/tool-choice-queue.ts +23 -0
- package/src/task/executor.ts +69 -6
- package/src/task/receipt.ts +5 -0
- package/src/task/render.ts +21 -1
- package/src/task/types.ts +8 -0
- package/src/thinking-metadata.ts +51 -0
- package/src/thinking.ts +26 -46
- package/src/tools/bash.ts +1 -1
- package/src/tools/index.ts +2 -0
- package/src/tools/resolve.ts +93 -18
- package/src/utils/edit-mode.ts +1 -1
- package/src/utils/tool-choice.ts +45 -16
|
@@ -116,6 +116,27 @@ const GJC_GOAL_SNAPSHOT_MAX_AGE_MILLISECONDS = 10 * 60 * 1000;
|
|
|
116
116
|
const GJC_GOAL_SNAPSHOT_MAX_FUTURE_SKEW_MILLISECONDS = 60 * 1000;
|
|
117
117
|
|
|
118
118
|
const SCHEDULABLE_STATUSES = new Set<UltragoalGoalStatus>(["pending", "active", "failed"]);
|
|
119
|
+
const NATIVE_STEERING_KINDS = [
|
|
120
|
+
"add_subgoal",
|
|
121
|
+
"split_subgoal",
|
|
122
|
+
"reorder_pending",
|
|
123
|
+
"revise_pending_wording",
|
|
124
|
+
"annotate_ledger",
|
|
125
|
+
"mark_blocked_superseded",
|
|
126
|
+
] as const;
|
|
127
|
+
type UltragoalSteeringKind = (typeof NATIVE_STEERING_KINDS)[number];
|
|
128
|
+
const NATIVE_STEERING_KIND_SET = new Set<string>(NATIVE_STEERING_KINDS);
|
|
129
|
+
|
|
130
|
+
interface ReplacementSpec {
|
|
131
|
+
title: string;
|
|
132
|
+
objective: string;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
interface SteeringCommandResult {
|
|
136
|
+
kind: UltragoalSteeringKind;
|
|
137
|
+
message: string;
|
|
138
|
+
receipt: JsonObject;
|
|
139
|
+
}
|
|
119
140
|
|
|
120
141
|
function stableStructuredValue(value: unknown): unknown {
|
|
121
142
|
if (Array.isArray(value)) return value.map(item => stableStructuredValue(item));
|
|
@@ -1263,44 +1284,372 @@ export async function checkpointAndContinueUltragoalGoal(input: {
|
|
|
1263
1284
|
};
|
|
1264
1285
|
}
|
|
1265
1286
|
|
|
1266
|
-
|
|
1287
|
+
function nextUltragoalGoalId(plan: UltragoalPlan, offset = 1): string {
|
|
1288
|
+
return `G${String(plan.goals.length + offset).padStart(3, "0")}`;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
function requireSteeringText(value: string, label: string, kind: UltragoalSteeringKind): string {
|
|
1292
|
+
const trimmed = value.trim();
|
|
1293
|
+
if (!trimmed) throw new Error(`steer --${label} is required for ${kind}`);
|
|
1294
|
+
return trimmed;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
function requireSteeringEvidence(input: { kind: UltragoalSteeringKind; evidence: string; rationale: string }): {
|
|
1298
|
+
evidence: string;
|
|
1299
|
+
rationale: string;
|
|
1300
|
+
} {
|
|
1301
|
+
return {
|
|
1302
|
+
evidence: requireSteeringText(input.evidence, "evidence", input.kind),
|
|
1303
|
+
rationale: requireSteeringText(input.rationale, "rationale", input.kind),
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
function findGoalOrThrow(plan: UltragoalPlan, goalId: string, kind: UltragoalSteeringKind): UltragoalGoal {
|
|
1308
|
+
const id = goalId.trim();
|
|
1309
|
+
if (!id) throw new Error(`steer --goal-id is required for ${kind}`);
|
|
1310
|
+
const goal = plan.goals.find(item => item.id === id);
|
|
1311
|
+
if (!goal) throw new Error(`No ultragoal goal found for ${id}.`);
|
|
1312
|
+
return goal;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
function requireGoalStatus(
|
|
1316
|
+
goal: UltragoalGoal,
|
|
1317
|
+
allowed: readonly UltragoalGoalStatus[],
|
|
1318
|
+
kind: UltragoalSteeringKind,
|
|
1319
|
+
): void {
|
|
1320
|
+
if (!allowed.includes(goal.status)) {
|
|
1321
|
+
throw new Error(`steer ${kind} requires goal ${goal.id} status ${allowed.join(" or ")}; found ${goal.status}`);
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
function parseJsonFlag(value: string, label: string, kind: UltragoalSteeringKind): unknown {
|
|
1326
|
+
const trimmed = requireSteeringText(value, label, kind);
|
|
1327
|
+
try {
|
|
1328
|
+
return JSON.parse(trimmed) as unknown;
|
|
1329
|
+
} catch (error) {
|
|
1330
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1331
|
+
throw new Error(`steer --${label} must be valid JSON for ${kind}: ${message}`);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
function parseReplacementSpecs(value: string, kind: UltragoalSteeringKind): ReplacementSpec[] {
|
|
1336
|
+
const raw = parseJsonFlag(value, "replacements-json", kind);
|
|
1337
|
+
if (!Array.isArray(raw) || raw.length < 2) {
|
|
1338
|
+
throw new Error("steer --replacements-json must be an array with at least two replacements");
|
|
1339
|
+
}
|
|
1340
|
+
const seen = new Set<string>();
|
|
1341
|
+
return raw.map((item, index) => {
|
|
1342
|
+
if (typeof item !== "object" || item === null || Array.isArray(item)) {
|
|
1343
|
+
throw new Error(`steer --replacements-json[${index}] must be an object`);
|
|
1344
|
+
}
|
|
1345
|
+
const record = item as Record<string, unknown>;
|
|
1346
|
+
const title = typeof record.title === "string" ? record.title.trim() : "";
|
|
1347
|
+
const objective = typeof record.objective === "string" ? record.objective.trim() : "";
|
|
1348
|
+
if (!title || !objective) {
|
|
1349
|
+
throw new Error(`steer --replacements-json[${index}] requires non-empty title and objective`);
|
|
1350
|
+
}
|
|
1351
|
+
const key = `${title}\u0000${objective}`;
|
|
1352
|
+
if (seen.has(key)) throw new Error(`steer --replacements-json[${index}] duplicates an earlier replacement`);
|
|
1353
|
+
seen.add(key);
|
|
1354
|
+
return { title, objective };
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
function parsePendingOrder(value: string, kind: UltragoalSteeringKind): string[] {
|
|
1359
|
+
const raw = parseJsonFlag(value, "order-json", kind);
|
|
1360
|
+
if (!Array.isArray(raw) || raw.length === 0) {
|
|
1361
|
+
throw new Error("steer --order-json must be a non-empty array of goal ids");
|
|
1362
|
+
}
|
|
1363
|
+
const seen = new Set<string>();
|
|
1364
|
+
return raw.map((item, index) => {
|
|
1365
|
+
if (typeof item !== "string" || item.trim().length === 0) {
|
|
1366
|
+
throw new Error(`steer --order-json[${index}] must be a non-empty goal id string`);
|
|
1367
|
+
}
|
|
1368
|
+
const id = item.trim();
|
|
1369
|
+
if (seen.has(id)) throw new Error(`steer --order-json contains duplicate goal id ${id}`);
|
|
1370
|
+
seen.add(id);
|
|
1371
|
+
return id;
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
async function appendSteeringRejected(input: {
|
|
1376
|
+
cwd: string;
|
|
1377
|
+
kind: UltragoalSteeringKind;
|
|
1378
|
+
reason: string;
|
|
1379
|
+
goalId?: string;
|
|
1380
|
+
evidence?: string;
|
|
1381
|
+
rationale?: string;
|
|
1382
|
+
payload?: JsonObject;
|
|
1383
|
+
}): Promise<void> {
|
|
1384
|
+
await appendLedger(input.cwd, {
|
|
1385
|
+
event: "steering_rejected",
|
|
1386
|
+
kind: input.kind,
|
|
1387
|
+
goalId: input.goalId?.trim() || undefined,
|
|
1388
|
+
reason: input.reason,
|
|
1389
|
+
evidence: input.evidence?.trim() || undefined,
|
|
1390
|
+
rationale: input.rationale?.trim() || undefined,
|
|
1391
|
+
payload: input.payload,
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
function steeringPayloadSummary(args: readonly string[]): JsonObject {
|
|
1396
|
+
return {
|
|
1397
|
+
goalId: flagValue(args, "--goal-id"),
|
|
1398
|
+
title: flagValue(args, "--title"),
|
|
1399
|
+
objective: flagValue(args, "--objective"),
|
|
1400
|
+
replacementsJson: flagValue(args, "--replacements-json"),
|
|
1401
|
+
orderJson: flagValue(args, "--order-json"),
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
function parseNativeSteeringKind(value: string | undefined): UltragoalSteeringKind {
|
|
1406
|
+
if (typeof value === "string" && NATIVE_STEERING_KIND_SET.has(value)) return value as UltragoalSteeringKind;
|
|
1407
|
+
throw new Error(`native steering currently supports --kind ${NATIVE_STEERING_KINDS.join(", ")}`);
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
async function addUltragoalSubgoalToPlan(input: {
|
|
1267
1411
|
cwd: string;
|
|
1412
|
+
plan: UltragoalPlan;
|
|
1268
1413
|
title: string;
|
|
1269
1414
|
objective: string;
|
|
1270
1415
|
evidence: string;
|
|
1271
1416
|
rationale: string;
|
|
1272
|
-
}): Promise<UltragoalPlan> {
|
|
1273
|
-
const
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
if (!value.trim()) throw new Error(`steer --${label} is required for add_subgoal`);
|
|
1282
|
-
}
|
|
1417
|
+
}): Promise<{ plan: UltragoalPlan; goalId: string }> {
|
|
1418
|
+
const kind = "add_subgoal";
|
|
1419
|
+
const title = requireSteeringText(input.title, "title", kind);
|
|
1420
|
+
const objective = requireSteeringText(input.objective, "objective", kind);
|
|
1421
|
+
const { evidence, rationale } = requireSteeringEvidence({
|
|
1422
|
+
kind,
|
|
1423
|
+
evidence: input.evidence,
|
|
1424
|
+
rationale: input.rationale,
|
|
1425
|
+
});
|
|
1283
1426
|
const now = new Date().toISOString();
|
|
1284
|
-
const nextId =
|
|
1285
|
-
plan.goals.push({
|
|
1427
|
+
const nextId = nextUltragoalGoalId(input.plan);
|
|
1428
|
+
input.plan.goals.push({
|
|
1286
1429
|
id: nextId,
|
|
1287
|
-
title
|
|
1288
|
-
objective
|
|
1430
|
+
title,
|
|
1431
|
+
objective,
|
|
1289
1432
|
status: "pending",
|
|
1290
1433
|
createdAt: now,
|
|
1291
1434
|
updatedAt: now,
|
|
1292
|
-
steering: { kind
|
|
1435
|
+
steering: { kind, evidence, rationale },
|
|
1293
1436
|
});
|
|
1294
|
-
plan.updatedAt = now;
|
|
1295
|
-
await writePlan(input.cwd, plan);
|
|
1437
|
+
input.plan.updatedAt = now;
|
|
1438
|
+
await writePlan(input.cwd, input.plan);
|
|
1296
1439
|
await appendLedger(input.cwd, {
|
|
1297
1440
|
event: "steering_accepted",
|
|
1298
|
-
kind
|
|
1441
|
+
kind,
|
|
1299
1442
|
goalId: nextId,
|
|
1300
|
-
evidence
|
|
1301
|
-
rationale
|
|
1443
|
+
evidence,
|
|
1444
|
+
rationale,
|
|
1302
1445
|
});
|
|
1303
|
-
return plan;
|
|
1446
|
+
return { plan: input.plan, goalId: nextId };
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
export async function addUltragoalSubgoal(input: {
|
|
1450
|
+
cwd: string;
|
|
1451
|
+
title: string;
|
|
1452
|
+
objective: string;
|
|
1453
|
+
evidence: string;
|
|
1454
|
+
rationale: string;
|
|
1455
|
+
}): Promise<UltragoalPlan> {
|
|
1456
|
+
const plan = await readUltragoalPlan(input.cwd);
|
|
1457
|
+
if (!plan) throw new Error("No ultragoal plan found. Run `gjc ultragoal create-goals --brief ...` first.");
|
|
1458
|
+
return (await addUltragoalSubgoalToPlan({ ...input, plan })).plan;
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
async function splitUltragoalSubgoal(input: {
|
|
1462
|
+
cwd: string;
|
|
1463
|
+
plan: UltragoalPlan;
|
|
1464
|
+
goalId: string;
|
|
1465
|
+
replacementsJson: string;
|
|
1466
|
+
evidence: string;
|
|
1467
|
+
rationale: string;
|
|
1468
|
+
}): Promise<{ plan: UltragoalPlan; goalId: string; replacementGoalIds: string[] }> {
|
|
1469
|
+
const kind = "split_subgoal";
|
|
1470
|
+
const { evidence, rationale } = requireSteeringEvidence({
|
|
1471
|
+
kind,
|
|
1472
|
+
evidence: input.evidence,
|
|
1473
|
+
rationale: input.rationale,
|
|
1474
|
+
});
|
|
1475
|
+
const target = findGoalOrThrow(input.plan, input.goalId, kind);
|
|
1476
|
+
requireGoalStatus(target, ["pending"], kind);
|
|
1477
|
+
const replacements = parseReplacementSpecs(input.replacementsJson, kind);
|
|
1478
|
+
const now = new Date().toISOString();
|
|
1479
|
+
const replacementGoalIds = replacements.map((_, index) => nextUltragoalGoalId(input.plan, index + 1));
|
|
1480
|
+
target.status = "superseded";
|
|
1481
|
+
target.evidence = evidence;
|
|
1482
|
+
target.updatedAt = now;
|
|
1483
|
+
target.steering = { kind, evidence, rationale, replacementGoalIds };
|
|
1484
|
+
const replacementGoals = replacements.map(
|
|
1485
|
+
(replacement, index): UltragoalGoal => ({
|
|
1486
|
+
id: replacementGoalIds[index]!,
|
|
1487
|
+
title: replacement.title,
|
|
1488
|
+
objective: replacement.objective,
|
|
1489
|
+
status: "pending",
|
|
1490
|
+
createdAt: now,
|
|
1491
|
+
updatedAt: now,
|
|
1492
|
+
steering: { kind: "split_replacement", sourceGoalId: target.id, evidence, rationale },
|
|
1493
|
+
}),
|
|
1494
|
+
);
|
|
1495
|
+
const targetIndex = input.plan.goals.findIndex(goal => goal.id === target.id);
|
|
1496
|
+
input.plan.goals.splice(targetIndex + 1, 0, ...replacementGoals);
|
|
1497
|
+
input.plan.updatedAt = now;
|
|
1498
|
+
await writePlan(input.cwd, input.plan);
|
|
1499
|
+
await appendLedger(input.cwd, {
|
|
1500
|
+
event: "steering_accepted",
|
|
1501
|
+
kind,
|
|
1502
|
+
goalId: target.id,
|
|
1503
|
+
replacementGoalIds,
|
|
1504
|
+
evidence,
|
|
1505
|
+
rationale,
|
|
1506
|
+
});
|
|
1507
|
+
return { plan: input.plan, goalId: target.id, replacementGoalIds };
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
async function reorderPendingUltragoalGoals(input: {
|
|
1511
|
+
cwd: string;
|
|
1512
|
+
plan: UltragoalPlan;
|
|
1513
|
+
orderJson: string;
|
|
1514
|
+
evidence: string;
|
|
1515
|
+
rationale: string;
|
|
1516
|
+
}): Promise<{ plan: UltragoalPlan; pendingGoalIds: string[] }> {
|
|
1517
|
+
const kind = "reorder_pending";
|
|
1518
|
+
const { evidence, rationale } = requireSteeringEvidence({
|
|
1519
|
+
kind,
|
|
1520
|
+
evidence: input.evidence,
|
|
1521
|
+
rationale: input.rationale,
|
|
1522
|
+
});
|
|
1523
|
+
const pendingGoalIds = input.plan.goals.filter(goal => goal.status === "pending").map(goal => goal.id);
|
|
1524
|
+
const requestedOrder = parsePendingOrder(input.orderJson, kind);
|
|
1525
|
+
const pendingSet = new Set(pendingGoalIds);
|
|
1526
|
+
for (const id of requestedOrder) {
|
|
1527
|
+
const goal = input.plan.goals.find(item => item.id === id);
|
|
1528
|
+
if (!goal) throw new Error(`steer --order-json references unknown goal id ${id}`);
|
|
1529
|
+
if (goal.status !== "pending") throw new Error(`steer --order-json references non-pending goal id ${id}`);
|
|
1530
|
+
}
|
|
1531
|
+
const missing = pendingGoalIds.filter(id => !requestedOrder.includes(id));
|
|
1532
|
+
if (missing.length > 0) throw new Error(`steer --order-json missing pending goal id(s): ${missing.join(", ")}`);
|
|
1533
|
+
if (requestedOrder.length !== pendingSet.size)
|
|
1534
|
+
throw new Error("steer --order-json must include every pending goal exactly once");
|
|
1535
|
+
const pendingById = new Map(input.plan.goals.map(goal => [goal.id, goal]));
|
|
1536
|
+
const remaining = [...requestedOrder];
|
|
1537
|
+
input.plan.goals = input.plan.goals.map(goal =>
|
|
1538
|
+
goal.status === "pending" ? pendingById.get(remaining.shift()!)! : goal,
|
|
1539
|
+
);
|
|
1540
|
+
input.plan.updatedAt = new Date().toISOString();
|
|
1541
|
+
await writePlan(input.cwd, input.plan);
|
|
1542
|
+
await appendLedger(input.cwd, {
|
|
1543
|
+
event: "steering_accepted",
|
|
1544
|
+
kind,
|
|
1545
|
+
previousPendingGoalIds: pendingGoalIds,
|
|
1546
|
+
pendingGoalIds: requestedOrder,
|
|
1547
|
+
evidence,
|
|
1548
|
+
rationale,
|
|
1549
|
+
});
|
|
1550
|
+
return { plan: input.plan, pendingGoalIds: requestedOrder };
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
async function revisePendingUltragoalWording(input: {
|
|
1554
|
+
cwd: string;
|
|
1555
|
+
plan: UltragoalPlan;
|
|
1556
|
+
goalId: string;
|
|
1557
|
+
title?: string;
|
|
1558
|
+
objective?: string;
|
|
1559
|
+
evidence: string;
|
|
1560
|
+
rationale: string;
|
|
1561
|
+
}): Promise<{ plan: UltragoalPlan; goalId: string; changedFields: string[] }> {
|
|
1562
|
+
const kind = "revise_pending_wording";
|
|
1563
|
+
const { evidence, rationale } = requireSteeringEvidence({
|
|
1564
|
+
kind,
|
|
1565
|
+
evidence: input.evidence,
|
|
1566
|
+
rationale: input.rationale,
|
|
1567
|
+
});
|
|
1568
|
+
const goal = findGoalOrThrow(input.plan, input.goalId, kind);
|
|
1569
|
+
requireGoalStatus(goal, ["pending"], kind);
|
|
1570
|
+
const title = input.title === undefined ? undefined : input.title.trim();
|
|
1571
|
+
const objective = input.objective === undefined ? undefined : input.objective.trim();
|
|
1572
|
+
if (input.title !== undefined && !title)
|
|
1573
|
+
throw new Error("steer --title must be non-empty for revise_pending_wording");
|
|
1574
|
+
if (input.objective !== undefined && !objective)
|
|
1575
|
+
throw new Error("steer --objective must be non-empty for revise_pending_wording");
|
|
1576
|
+
if (!title && !objective) throw new Error("revise_pending_wording requires --title and/or --objective");
|
|
1577
|
+
const changedFields: string[] = [];
|
|
1578
|
+
if (title !== undefined) {
|
|
1579
|
+
goal.title = title;
|
|
1580
|
+
changedFields.push("title");
|
|
1581
|
+
}
|
|
1582
|
+
if (objective !== undefined) {
|
|
1583
|
+
goal.objective = objective;
|
|
1584
|
+
changedFields.push("objective");
|
|
1585
|
+
}
|
|
1586
|
+
const now = new Date().toISOString();
|
|
1587
|
+
goal.updatedAt = now;
|
|
1588
|
+
goal.steering = { kind, evidence, rationale, changedFields };
|
|
1589
|
+
input.plan.updatedAt = now;
|
|
1590
|
+
await writePlan(input.cwd, input.plan);
|
|
1591
|
+
await appendLedger(input.cwd, {
|
|
1592
|
+
event: "steering_accepted",
|
|
1593
|
+
kind,
|
|
1594
|
+
goalId: goal.id,
|
|
1595
|
+
changedFields,
|
|
1596
|
+
evidence,
|
|
1597
|
+
rationale,
|
|
1598
|
+
});
|
|
1599
|
+
return { plan: input.plan, goalId: goal.id, changedFields };
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
async function annotateUltragoalLedger(input: {
|
|
1603
|
+
cwd: string;
|
|
1604
|
+
plan: UltragoalPlan;
|
|
1605
|
+
evidence: string;
|
|
1606
|
+
rationale: string;
|
|
1607
|
+
}): Promise<{ plan: UltragoalPlan }> {
|
|
1608
|
+
const kind = "annotate_ledger";
|
|
1609
|
+
const { evidence, rationale } = requireSteeringEvidence({
|
|
1610
|
+
kind,
|
|
1611
|
+
evidence: input.evidence,
|
|
1612
|
+
rationale: input.rationale,
|
|
1613
|
+
});
|
|
1614
|
+
await appendLedger(input.cwd, { event: "steering_accepted", kind, evidence, rationale });
|
|
1615
|
+
return { plan: input.plan };
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
async function markBlockedUltragoalSuperseded(input: {
|
|
1619
|
+
cwd: string;
|
|
1620
|
+
plan: UltragoalPlan;
|
|
1621
|
+
goalId: string;
|
|
1622
|
+
evidence: string;
|
|
1623
|
+
rationale: string;
|
|
1624
|
+
}): Promise<{ plan: UltragoalPlan; goalId: string }> {
|
|
1625
|
+
const kind = "mark_blocked_superseded";
|
|
1626
|
+
const { evidence, rationale } = requireSteeringEvidence({
|
|
1627
|
+
kind,
|
|
1628
|
+
evidence: input.evidence,
|
|
1629
|
+
rationale: input.rationale,
|
|
1630
|
+
});
|
|
1631
|
+
const goal = findGoalOrThrow(input.plan, input.goalId, kind);
|
|
1632
|
+
requireGoalStatus(goal, ["blocked", "review_blocked"], kind);
|
|
1633
|
+
const remainingRequiredGoals = requiredUltragoalGoals(input.plan).filter(item => item.id !== goal.id);
|
|
1634
|
+
if (remainingRequiredGoals.length === 0) {
|
|
1635
|
+
throw new Error(`steer ${kind} cannot supersede ${goal.id} because it is the only remaining required goal`);
|
|
1636
|
+
}
|
|
1637
|
+
const now = new Date().toISOString();
|
|
1638
|
+
goal.status = "superseded";
|
|
1639
|
+
goal.evidence = evidence;
|
|
1640
|
+
goal.updatedAt = now;
|
|
1641
|
+
goal.steering = { kind, evidence, rationale, noReplacementRequired: true };
|
|
1642
|
+
input.plan.updatedAt = now;
|
|
1643
|
+
await writePlan(input.cwd, input.plan);
|
|
1644
|
+
await appendLedger(input.cwd, {
|
|
1645
|
+
event: "steering_accepted",
|
|
1646
|
+
kind,
|
|
1647
|
+
goalId: goal.id,
|
|
1648
|
+
noReplacementRequired: true,
|
|
1649
|
+
evidence,
|
|
1650
|
+
rationale,
|
|
1651
|
+
});
|
|
1652
|
+
return { plan: input.plan, goalId: goal.id };
|
|
1304
1653
|
}
|
|
1305
1654
|
|
|
1306
1655
|
export async function recordUltragoalReviewBlockers(input: {
|
|
@@ -1365,6 +1714,8 @@ const FLAGS_WITH_VALUES = new Set([
|
|
|
1365
1714
|
"--title",
|
|
1366
1715
|
"--objective",
|
|
1367
1716
|
"--rationale",
|
|
1717
|
+
"--replacements-json",
|
|
1718
|
+
"--order-json",
|
|
1368
1719
|
]);
|
|
1369
1720
|
|
|
1370
1721
|
function isHelpArg(arg: string): boolean {
|
|
@@ -1523,6 +1874,135 @@ function renderCheckpointContinuation(
|
|
|
1523
1874
|
return lines.join("\n");
|
|
1524
1875
|
}
|
|
1525
1876
|
|
|
1877
|
+
async function executeUltragoalSteeringCommand(args: readonly string[], cwd: string): Promise<SteeringCommandResult> {
|
|
1878
|
+
const kind = parseNativeSteeringKind(flagValue(args, "--kind"));
|
|
1879
|
+
const plan = await readUltragoalPlan(cwd);
|
|
1880
|
+
if (!plan) throw new Error("No ultragoal plan found. Run `gjc ultragoal create-goals --brief ...` first.");
|
|
1881
|
+
const evidence = flagValue(args, "--evidence") ?? "";
|
|
1882
|
+
const rationale = flagValue(args, "--rationale") ?? "";
|
|
1883
|
+
try {
|
|
1884
|
+
switch (kind) {
|
|
1885
|
+
case "add_subgoal": {
|
|
1886
|
+
const result = await addUltragoalSubgoalToPlan({
|
|
1887
|
+
cwd,
|
|
1888
|
+
plan,
|
|
1889
|
+
title: flagValue(args, "--title") ?? "",
|
|
1890
|
+
objective: flagValue(args, "--objective") ?? "",
|
|
1891
|
+
evidence,
|
|
1892
|
+
rationale,
|
|
1893
|
+
});
|
|
1894
|
+
return {
|
|
1895
|
+
kind,
|
|
1896
|
+
message: "Accepted add_subgoal steering.\n",
|
|
1897
|
+
receipt: { ok: true, kind, goal_id: result.goalId, goals_path: getUltragoalPaths(cwd).goalsPath },
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1900
|
+
case "split_subgoal": {
|
|
1901
|
+
const result = await splitUltragoalSubgoal({
|
|
1902
|
+
cwd,
|
|
1903
|
+
plan,
|
|
1904
|
+
goalId: flagValue(args, "--goal-id") ?? "",
|
|
1905
|
+
replacementsJson: flagValue(args, "--replacements-json") ?? "",
|
|
1906
|
+
evidence,
|
|
1907
|
+
rationale,
|
|
1908
|
+
});
|
|
1909
|
+
return {
|
|
1910
|
+
kind,
|
|
1911
|
+
message: "Accepted split_subgoal steering.\n",
|
|
1912
|
+
receipt: {
|
|
1913
|
+
ok: true,
|
|
1914
|
+
kind,
|
|
1915
|
+
goal_id: result.goalId,
|
|
1916
|
+
replacement_goal_ids: result.replacementGoalIds,
|
|
1917
|
+
goals_path: getUltragoalPaths(cwd).goalsPath,
|
|
1918
|
+
},
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1921
|
+
case "reorder_pending": {
|
|
1922
|
+
const result = await reorderPendingUltragoalGoals({
|
|
1923
|
+
cwd,
|
|
1924
|
+
plan,
|
|
1925
|
+
orderJson: flagValue(args, "--order-json") ?? "",
|
|
1926
|
+
evidence,
|
|
1927
|
+
rationale,
|
|
1928
|
+
});
|
|
1929
|
+
return {
|
|
1930
|
+
kind,
|
|
1931
|
+
message: "Accepted reorder_pending steering.\n",
|
|
1932
|
+
receipt: {
|
|
1933
|
+
ok: true,
|
|
1934
|
+
kind,
|
|
1935
|
+
pending_goal_ids: result.pendingGoalIds,
|
|
1936
|
+
goals_path: getUltragoalPaths(cwd).goalsPath,
|
|
1937
|
+
},
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
case "revise_pending_wording": {
|
|
1941
|
+
const result = await revisePendingUltragoalWording({
|
|
1942
|
+
cwd,
|
|
1943
|
+
plan,
|
|
1944
|
+
goalId: flagValue(args, "--goal-id") ?? "",
|
|
1945
|
+
title: flagValue(args, "--title"),
|
|
1946
|
+
objective: flagValue(args, "--objective"),
|
|
1947
|
+
evidence,
|
|
1948
|
+
rationale,
|
|
1949
|
+
});
|
|
1950
|
+
return {
|
|
1951
|
+
kind,
|
|
1952
|
+
message: "Accepted revise_pending_wording steering.\n",
|
|
1953
|
+
receipt: {
|
|
1954
|
+
ok: true,
|
|
1955
|
+
kind,
|
|
1956
|
+
goal_id: result.goalId,
|
|
1957
|
+
changed_fields: result.changedFields,
|
|
1958
|
+
goals_path: getUltragoalPaths(cwd).goalsPath,
|
|
1959
|
+
},
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1962
|
+
case "annotate_ledger": {
|
|
1963
|
+
await annotateUltragoalLedger({ cwd, plan, evidence, rationale });
|
|
1964
|
+
return {
|
|
1965
|
+
kind,
|
|
1966
|
+
message: "Accepted annotate_ledger steering.\n",
|
|
1967
|
+
receipt: { ok: true, kind, ledger_path: getUltragoalPaths(cwd).ledgerPath },
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1970
|
+
case "mark_blocked_superseded": {
|
|
1971
|
+
const result = await markBlockedUltragoalSuperseded({
|
|
1972
|
+
cwd,
|
|
1973
|
+
plan,
|
|
1974
|
+
goalId: flagValue(args, "--goal-id") ?? "",
|
|
1975
|
+
evidence,
|
|
1976
|
+
rationale,
|
|
1977
|
+
});
|
|
1978
|
+
return {
|
|
1979
|
+
kind,
|
|
1980
|
+
message: "Accepted mark_blocked_superseded steering.\n",
|
|
1981
|
+
receipt: {
|
|
1982
|
+
ok: true,
|
|
1983
|
+
kind,
|
|
1984
|
+
goal_id: result.goalId,
|
|
1985
|
+
no_replacement_required: true,
|
|
1986
|
+
goals_path: getUltragoalPaths(cwd).goalsPath,
|
|
1987
|
+
},
|
|
1988
|
+
};
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
} catch (error) {
|
|
1992
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1993
|
+
await appendSteeringRejected({
|
|
1994
|
+
cwd,
|
|
1995
|
+
kind,
|
|
1996
|
+
reason,
|
|
1997
|
+
goalId: flagValue(args, "--goal-id"),
|
|
1998
|
+
evidence,
|
|
1999
|
+
rationale,
|
|
2000
|
+
payload: steeringPayloadSummary(args),
|
|
2001
|
+
});
|
|
2002
|
+
throw error;
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
|
|
1526
2006
|
async function dispatchUltragoalCommand(args: string[], cwd: string): Promise<UltragoalCommandResult> {
|
|
1527
2007
|
const help = renderUltragoalHelp(args);
|
|
1528
2008
|
if (help) return { status: 0, stdout: help };
|
|
@@ -1577,26 +2057,10 @@ async function dispatchUltragoalCommand(args: string[], cwd: string): Promise<Ul
|
|
|
1577
2057
|
};
|
|
1578
2058
|
}
|
|
1579
2059
|
case "steer": {
|
|
1580
|
-
const
|
|
1581
|
-
if (kind !== "add_subgoal") throw new Error("native steering currently supports --kind add_subgoal");
|
|
1582
|
-
const plan = await addUltragoalSubgoal({
|
|
1583
|
-
cwd,
|
|
1584
|
-
title: flagValue(args, "--title") ?? "",
|
|
1585
|
-
objective: flagValue(args, "--objective") ?? "",
|
|
1586
|
-
evidence: flagValue(args, "--evidence") ?? "",
|
|
1587
|
-
rationale: flagValue(args, "--rationale") ?? "",
|
|
1588
|
-
});
|
|
1589
|
-
const goal = plan.goals.at(-1);
|
|
2060
|
+
const result = await executeUltragoalSteeringCommand(args, cwd);
|
|
1590
2061
|
return {
|
|
1591
2062
|
status: 0,
|
|
1592
|
-
stdout: json
|
|
1593
|
-
? renderCliWriteReceipt({
|
|
1594
|
-
ok: true,
|
|
1595
|
-
kind,
|
|
1596
|
-
goal_id: goal?.id,
|
|
1597
|
-
goals_path: getUltragoalPaths(cwd).goalsPath,
|
|
1598
|
-
})
|
|
1599
|
-
: "Accepted add_subgoal steering.\n",
|
|
2063
|
+
stdout: json ? renderCliWriteReceipt(result.receipt) : result.message,
|
|
1600
2064
|
};
|
|
1601
2065
|
}
|
|
1602
2066
|
case "record-review-blockers": {
|
|
@@ -1464,12 +1464,24 @@
|
|
|
1464
1464
|
"name": "quality-gate-json",
|
|
1465
1465
|
"type": "string"
|
|
1466
1466
|
},
|
|
1467
|
+
{
|
|
1468
|
+
"appliesToVerbs": [
|
|
1469
|
+
"steer"
|
|
1470
|
+
],
|
|
1471
|
+
"name": "goal-id",
|
|
1472
|
+
"type": "string"
|
|
1473
|
+
},
|
|
1467
1474
|
{
|
|
1468
1475
|
"appliesToVerbs": [
|
|
1469
1476
|
"steer"
|
|
1470
1477
|
],
|
|
1471
1478
|
"enumValues": [
|
|
1472
|
-
"add_subgoal"
|
|
1479
|
+
"add_subgoal",
|
|
1480
|
+
"split_subgoal",
|
|
1481
|
+
"reorder_pending",
|
|
1482
|
+
"revise_pending_wording",
|
|
1483
|
+
"annotate_ledger",
|
|
1484
|
+
"mark_blocked_superseded"
|
|
1473
1485
|
],
|
|
1474
1486
|
"name": "kind",
|
|
1475
1487
|
"type": "enum"
|
|
@@ -1497,6 +1509,20 @@
|
|
|
1497
1509
|
"name": "rationale",
|
|
1498
1510
|
"type": "string"
|
|
1499
1511
|
},
|
|
1512
|
+
{
|
|
1513
|
+
"appliesToVerbs": [
|
|
1514
|
+
"steer"
|
|
1515
|
+
],
|
|
1516
|
+
"name": "replacements-json",
|
|
1517
|
+
"type": "string"
|
|
1518
|
+
},
|
|
1519
|
+
{
|
|
1520
|
+
"appliesToVerbs": [
|
|
1521
|
+
"steer"
|
|
1522
|
+
],
|
|
1523
|
+
"name": "order-json",
|
|
1524
|
+
"type": "string"
|
|
1525
|
+
},
|
|
1500
1526
|
{
|
|
1501
1527
|
"appliesToVerbs": [
|
|
1502
1528
|
"status",
|
|
@@ -267,10 +267,25 @@ export const WORKFLOW_MANIFEST: Record<CanonicalGjcWorkflowSkill, SkillManifest>
|
|
|
267
267
|
},
|
|
268
268
|
{ name: "gjc-goal-json", type: "string", appliesToVerbs: ["checkpoint", "record-review-blockers"] },
|
|
269
269
|
{ name: "quality-gate-json", type: "string", appliesToVerbs: ["checkpoint"] },
|
|
270
|
-
{ name: "
|
|
270
|
+
{ name: "goal-id", type: "string", appliesToVerbs: ["steer"] },
|
|
271
|
+
{
|
|
272
|
+
name: "kind",
|
|
273
|
+
type: "enum",
|
|
274
|
+
enumValues: [
|
|
275
|
+
"add_subgoal",
|
|
276
|
+
"split_subgoal",
|
|
277
|
+
"reorder_pending",
|
|
278
|
+
"revise_pending_wording",
|
|
279
|
+
"annotate_ledger",
|
|
280
|
+
"mark_blocked_superseded",
|
|
281
|
+
],
|
|
282
|
+
appliesToVerbs: ["steer"],
|
|
283
|
+
},
|
|
271
284
|
{ name: "title", type: "string", appliesToVerbs: ["record-review-blockers", "steer"] },
|
|
272
285
|
{ name: "objective", type: "string", appliesToVerbs: ["record-review-blockers", "steer"] },
|
|
273
286
|
{ name: "rationale", type: "string", appliesToVerbs: ["steer"] },
|
|
287
|
+
{ name: "replacements-json", type: "string", appliesToVerbs: ["steer"] },
|
|
288
|
+
{ name: "order-json", type: "string", appliesToVerbs: ["steer"] },
|
|
274
289
|
{
|
|
275
290
|
name: "json",
|
|
276
291
|
type: "boolean",
|