@juangcarmona/lighthouse-sdlc 0.18.7 → 0.18.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/github-actions.md +2 -0
- package/package.json +1 -1
- package/src/install-workflows.mjs +1 -1
- package/src/issue-actions.mjs +19 -8
- package/src/stage-dispatch.mjs +9 -4
- package/src/stage-transition.mjs +37 -3
- package/workflows/lighthouse-sdlc-product-change.lock.yml +6 -1
- package/workflows/lighthouse-sdlc-product-change.md +5 -0
- package/workflows/lighthouse-sdlc-router.yml +1 -1
package/github-actions.md
CHANGED
|
@@ -50,6 +50,8 @@ Successful refinement explicitly wakes the router after publication and reservat
|
|
|
50
50
|
|
|
51
51
|
For acceptance or deliberate event replay, the owner can dispatch the router with `issue`, the expected next `stage` and `after_run` set to the completed execution ID. The bot can request authenticated refinement-to-implementation, product-prerequisite and applied-product parent continuations. Each request is a wake-up: the router verifies the originating run and its retained authority. Both use the same fresh observations and deduplication as `workflow_run`; neither can force a transition or resend an uncertain request. If the bounded wait expires, inspect the source run and replay after it completes. An expired request artifact, changed source, changed intent or edited receipt blocks admission. Request artifacts last 14 days. Inspect origin and worker runs before reconciling uncertainty; there is no database, persistent dispatcher or SoFa dependency.
|
|
52
52
|
|
|
53
|
+
When an authenticated Product Change worker finds that accepted product intent already covers its parent request, the router can resume the original stage if the parent has no existing PR. It verifies any owner answer against its original question. The original scope and automatic implementation setting still apply. A repeated product gap stops for maintainer review instead of starting another alignment cycle. Parents with an existing PR need a separate continuation path.
|
|
54
|
+
|
|
53
55
|
The worker creates the OpenSpec plan, implements it and runs checks in one workspace. The PR contains both the plan and code. A separate proposal PR remains available for design-only work; merging one is not an implementation prerequisite.
|
|
54
56
|
|
|
55
57
|
By default, repositories with a `verify` npm script use `npm ci --ignore-scripts` and `npm run verify`. Other repositories configure checks once in `.lighthouse/implement.json`:
|
package/package.json
CHANGED
|
@@ -18,7 +18,7 @@ for (const name of files) {
|
|
|
18
18
|
if (name === 'lighthouse-sdlc-router.yml') {
|
|
19
19
|
const enabled = ['triage', 'refine', ...(propose ? ['propose'] : []), ...(implement ? ['implement'] : []), ...(completion ? ['integrate', 'review'] : [])];
|
|
20
20
|
content = content.replace('LIGHTHOUSE_ENABLED_STAGES: triage,refine,propose,implement,integrate,review', `LIGHTHOUSE_ENABLED_STAGES: ${enabled.join(',')}`).replace('options: [triage, refine, propose, implement, integrate, review]', `options: [${enabled.join(', ')}]`);
|
|
21
|
-
content = content.replace('options: [stage, product-change, resume-product]', `options: [stage${productChange ? ', product-change, resume-product' : ''}]`).replace('LIGHTHOUSE_PRODUCT_CHANGE_ENABLED: false', `LIGHTHOUSE_PRODUCT_CHANGE_ENABLED: ${productChange}`);
|
|
21
|
+
content = content.replace('options: [stage, product-change, resume-product, resume-aligned]', `options: [stage${productChange ? ', product-change, resume-product, resume-aligned' : ''}]`).replace('LIGHTHOUSE_PRODUCT_CHANGE_ENABLED: false', `LIGHTHOUSE_PRODUCT_CHANGE_ENABLED: ${productChange}`);
|
|
22
22
|
if (!implement) content = content.replace('LIGHTHOUSE_AUTO_IMPLEMENT: true', 'LIGHTHOUSE_AUTO_IMPLEMENT: false');
|
|
23
23
|
if (!completion) content = content.replaceAll('LIGHTHOUSE_COMPLETION_ENABLED: true', 'LIGHTHOUSE_COMPLETION_ENABLED: false');
|
|
24
24
|
}
|
package/src/issue-actions.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { refinementAfterRun, signalRefinementContinuation, productGapAfterRun, signalProductGap, signalProductApplyContinuation, productApplyAfterRun } from './stage-transition.mjs';
|
|
1
|
+
import { refinementAfterRun, signalRefinementContinuation, productAlignmentAfterRun, signalProductAlignment, productGapAfterRun, signalProductGap, signalProductApplyContinuation, productApplyAfterRun } from './stage-transition.mjs';
|
|
2
2
|
import { dispatchId, dispatchArtifact, recordDispatch, sendDispatch, admitDispatch, loadDispatch, claimDispatch, checkDispatchClaim, finishDispatch, completedEvent, readDispatchReceipt, issueComments, stages, executionModelSelection, isBotEpicTriage, epicTriageKey } from './stage-dispatch.mjs';
|
|
3
3
|
import { readActionsJson } from './actions-artifact.mjs';
|
|
4
4
|
import { deliveryAfterChecks, dispatchDeliveryChecks } from './delivery-events.mjs';
|
|
@@ -152,6 +152,7 @@ export function refinementNextAction({ stage, status, autoImplement, paused }) {
|
|
|
152
152
|
}
|
|
153
153
|
export function checkPublication(snapshot, issue, comments, revision) {
|
|
154
154
|
if (issue.state !== 'open' || issue.pull_request || revision !== (snapshot.baseRevision ?? snapshot.sourceRevision)) throw Error('Issue closed or source revision changed');
|
|
155
|
+
if (snapshot.productAlignment && issue.labels.some(x => x.name === 'future')) throw Error('Product alignment continuation was paused');
|
|
155
156
|
if (issue.labels.some(x => x.name === 'human-working') || !issue.labels.some(x => x.name === 'agent-working')) throw Error('Execution reservation revoked');
|
|
156
157
|
if (taskDigest(issue, comments) !== snapshot.taskDigest) throw Error('Issue or owner answers changed; request refinement again');
|
|
157
158
|
}
|
|
@@ -277,8 +278,9 @@ async function main(command) {
|
|
|
277
278
|
const after = ['route', 'dispatch-prepare', 'prepare'].includes(command) ? await completedEvent(api, event, env) : null;
|
|
278
279
|
if (after) {
|
|
279
280
|
const productResume = env.LIGHTHOUSE_PRODUCT_CHANGE_ENABLED === 'true' ? await productApplyAfterRun(api, repo, owner, after) : null;
|
|
280
|
-
const
|
|
281
|
-
const
|
|
281
|
+
const productAlignment = !productResume && env.LIGHTHOUSE_PRODUCT_CHANGE_ENABLED === 'true' ? await productAlignmentAfterRun(api, repo, owner, after) : null;
|
|
282
|
+
const productParent = !productResume && !productAlignment && env.LIGHTHOUSE_PRODUCT_CHANGE_ENABLED === 'true' ? await productGapAfterRun(api, repo, owner, after) : null;
|
|
283
|
+
const next = productResume ? { issue: productResume.issue, stage: productResume.stage, ...(productResume.parent.review ? { worker: 'apply-review' } : {}), productResume, reason: 'The approved product prerequisite was applied; resuming its authenticated parent.' } : productAlignment ? { issue: productAlignment.issue, stage: productAlignment.stage, productAlignment, reason: 'Accepted product intent covers the authenticated parent request.' } : productParent ? { issue: productParent.issue, stage: productParent.stage, worker: 'product-change', productParent, reason: 'An authenticated issue stage reported a product prerequisite.' } : await refinementAfterRun(api, repo, owner, after) ?? (env.LIGHTHOUSE_COMPLETION_ENABLED === 'true' ? await deliveryAfterChecks(api, repo, owner, after) : null);
|
|
282
284
|
route = next ? { ...next, run: true, reason: next.reason ?? 'Checks passed; continuing delivery.' } : { run: false, reason: 'No delivery action needed.' };
|
|
283
285
|
if (event.inputs?.after_run && route.run && (Number(event.inputs.issue) !== route.issue || event.inputs.stage !== route.stage)) throw Error('Completion replay target differs from the verified continuation');
|
|
284
286
|
}
|
|
@@ -349,6 +351,7 @@ async function main(command) {
|
|
|
349
351
|
const metadata = await api(''), revision = (await api(`commits/${metadata.default_branch}`)).sha;
|
|
350
352
|
if ((!productEntry && !eligibleIssueAuthor(issue, owner, stage)) || issue.state !== 'open' && stage !== 'review' || issue.pull_request || issue.labels.some(x => x.name === 'human-working' || x.name === 'agent-working' && !queue)) throw Error('Issue not eligible for dispatch');
|
|
351
353
|
if (route.productResume && (route.productResume.baseRevision !== revision || (route.productResume.parent.feedback?.taskDigest ?? route.productResume.parent.taskDigest) !== taskDigest(issue, answers) || issue.labels.some(x => ['future', 'needs-info', 'waiting-review'].includes(x.name)))) throw Error('Applied product parent, intent or authorization changed');
|
|
354
|
+
if (route.productAlignment && (route.productAlignment.baseRevision !== revision || (route.productAlignment.parent.feedback?.taskDigest ?? route.productAlignment.parent.taskDigest) !== taskDigest(issue, answers) || issue.labels.some(x => ['future', 'needs-info', 'waiting-review'].includes(x.name)))) throw Error('Aligned product parent, intent or authorization changed');
|
|
352
355
|
if (route.productParent && (route.productParent.baseRevision !== revision || route.productParent.taskDigest !== taskDigest(issue, answers) || issue.labels.some(x => ['future', 'needs-info', 'waiting-review'].includes(x.name)))) throw Error('Product prerequisite parent, intent or authorization changed');
|
|
353
356
|
if (handoff) {
|
|
354
357
|
const prior = await readActionsJson(api, repo, route.predecessor.runId, `lighthouse-input-${issueNumber}-${route.predecessor.attempt}`, 'request.json');
|
|
@@ -366,7 +369,7 @@ async function main(command) {
|
|
|
366
369
|
// Review events can run on a PR merge ref, which is not a dispatchable branch.
|
|
367
370
|
// Only an explicit owner dispatch selects a workflow branch; native events use main.
|
|
368
371
|
const manual = env.GITHUB_EVENT_NAME === 'workflow_dispatch';
|
|
369
|
-
const request = { schemaVersion: 1, id: dispatchId(repo, epicIdentity ?? feedbackIdentity ?? eventRunId ?? env.GITHUB_RUN_ID, stage), ...(epicIdentity ? { epicKey: epicIdentity } : {}), ...(epicContext ? { epic: epicContext } : {}), ...(worker ? { worker, ...(feedbackIdentity ? { feedbackKey: feedbackIdentity } : {}) } : {}), ...(route.productApproval?.approval ? { productApproval: { ...route.productApproval.approval, pullRequest: route.productApproval.pullRequest, head: route.productApproval.head, issue: route.productApproval.issue, changeName: route.productApproval.changeName } } : {}), ...(route.productFeedback ? { productFeedback: route.productFeedback } : {}), ...(route.productParent ? { productParent: route.productParent } : {}), ...(route.productResume ? { productResume: route.productResume } : {}), ...(route.productReview && !route.productApproval?.approval ? { productReview: { pullRequest: route.productReview.pullRequest, head: route.productReview.head, issue: route.productReview.issue, changeName: route.productReview.changeName, ...(route.productReview.checkpoint?.parent ? { parent: route.productReview.checkpoint.parent } : {}) } } : {}), repository: repo, issue: issueNumber, stage, routerRunId: env.GITHUB_RUN_ID, routerAttempt: Number(env.GITHUB_RUN_ATTEMPT), routerRevision: router.head_sha, routerRef: router.head_branch, workflowRevision: manual ? router.head_sha : revision, sourceRevision: revision, ref: manual ? router.head_branch : metadata.default_branch, eventName: env.GITHUB_EVENT_NAME, event, handoff, queue, ...(handoff ? { predecessor: route.predecessor } : {}), ...(eventRunId ? { eventRunId } : {}), autoImplement: !epicContext && stage === 'refine' && env.LIGHTHOUSE_AUTO_IMPLEMENT === 'true' && (!route.productResume || route.productResume.parent.autoImplement), productChangeEnabled: env.LIGHTHOUSE_PRODUCT_CHANGE_ENABLED === 'true', taskDigest: taskDigest(issue, answers) };
|
|
372
|
+
const request = { schemaVersion: 1, id: dispatchId(repo, epicIdentity ?? feedbackIdentity ?? eventRunId ?? env.GITHUB_RUN_ID, stage), ...(epicIdentity ? { epicKey: epicIdentity } : {}), ...(epicContext ? { epic: epicContext } : {}), ...(worker ? { worker, ...(feedbackIdentity ? { feedbackKey: feedbackIdentity } : {}) } : {}), ...(route.productApproval?.approval ? { productApproval: { ...route.productApproval.approval, pullRequest: route.productApproval.pullRequest, head: route.productApproval.head, issue: route.productApproval.issue, changeName: route.productApproval.changeName } } : {}), ...(route.productFeedback ? { productFeedback: route.productFeedback } : {}), ...(route.productParent ? { productParent: route.productParent } : {}), ...(route.productResume ? { productResume: route.productResume } : {}), ...(route.productAlignment ? { productAlignment: route.productAlignment } : {}), ...(route.productReview && !route.productApproval?.approval ? { productReview: { pullRequest: route.productReview.pullRequest, head: route.productReview.head, issue: route.productReview.issue, changeName: route.productReview.changeName, ...(route.productReview.checkpoint?.parent ? { parent: route.productReview.checkpoint.parent } : {}) } } : {}), repository: repo, issue: issueNumber, stage, routerRunId: env.GITHUB_RUN_ID, routerAttempt: Number(env.GITHUB_RUN_ATTEMPT), routerRevision: router.head_sha, routerRef: router.head_branch, workflowRevision: manual ? router.head_sha : revision, sourceRevision: revision, ref: manual ? router.head_branch : metadata.default_branch, eventName: env.GITHUB_EVENT_NAME, event, handoff, queue, ...(handoff ? { predecessor: route.predecessor } : {}), ...(eventRunId ? { eventRunId } : {}), autoImplement: !epicContext && stage === 'refine' && env.LIGHTHOUSE_AUTO_IMPLEMENT === 'true' && (!route.productResume || route.productResume.parent.autoImplement) && (!route.productAlignment || route.productAlignment.parent.autoImplement), productChangeEnabled: env.LIGHTHOUSE_PRODUCT_CHANGE_ENABLED === 'true', taskDigest: taskDigest(issue, answers) };
|
|
370
373
|
executionModelSelection(request);
|
|
371
374
|
const recorded = await recordDispatch(api, repo, request);
|
|
372
375
|
await output('send', String(recorded.send));
|
|
@@ -482,7 +485,7 @@ async function main(command) {
|
|
|
482
485
|
const productChangeName = productWorker ? dispatch.request.productFeedback?.changeName ?? dispatch.request.event.inputs?.change_name?.trim() ?? `issue-${issueNumber}-product-change` : undefined;
|
|
483
486
|
if (productWorker && !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(productChangeName)) throw Error('Invalid Product Change name');
|
|
484
487
|
const baselineDigest = productWorker ? await productModelDigest(process.cwd()) : undefined;
|
|
485
|
-
const snapshot = { ...(dispatch?.request.epic ? { epic: dispatch.request.epic } : {}), ...(dispatch && executionModelSelection(dispatch.request) ? { modelSelection: executionModelSelection(dispatch.request) } : {}), autoImplement: env.LIGHTHOUSE_AUTO_IMPLEMENT === 'true', productChangeEnabled: dispatch?.request.productChangeEnabled === true, ...(dispatch ? { worker: dispatch.request.worker, workflowRevision: env.GITHUB_SHA, dispatch: { commentId: dispatch.commentId, requestId: dispatch.request.id, digest: dispatch.receipt.digest, routerRunId: dispatch.request.routerRunId, routerAttempt: dispatch.request.routerAttempt } } : {}), ...(productWorker ? { authorizationDigest, policyDigest, productChangeName, baselineDigest, productEntry: dispatch.request.productFeedback ? 'feedback' : dispatch.request.productParent ? 'parent-handoff' : 'idea', ...((dispatch.request.productParent ?? dispatch.request.productReview?.parent ?? continuation?.request.productParent) ? { productParent: dispatch.request.productParent ?? dispatch.request.productReview?.parent ?? continuation.request.productParent } : {}) } : {}), ...(planningReference ? { planningReference } : {}), ...(productResume ? { productResume, baseRevision } : {}), ...(implementationReview ? { implementationReview, baseRevision } : epicImplementation ? { baseRevision } : {}), ...(proposalReview ? { proposalReview, ...(proposalReview.productProposal ? { baseRevision } : {}) } : {}), repository: repo, runId: id, issue: issueNumber, stage, title: issue.title, defaultBranch: repository.default_branch, sourceRevision: revision.sha, taskDigest: taskDigest(issue, answers), workflowRunId: env.GITHUB_RUN_ID, ...(predecessor ? { refinement: predecessor } : {}), ...(continuation ? { replyTo: continuation.request.runId, commentId: continuationAnswer.id, answerHistory: continuationHistory(continuation, continuationAnswer) } : {}), ...(implementation ? { implementation } : {}), ...(seed ? { seedDigest: changesDigest(seed) } : {}) };
|
|
488
|
+
const snapshot = { ...(dispatch?.request.epic ? { epic: dispatch.request.epic } : {}), ...(dispatch && executionModelSelection(dispatch.request) ? { modelSelection: executionModelSelection(dispatch.request) } : {}), autoImplement: env.LIGHTHOUSE_AUTO_IMPLEMENT === 'true', productChangeEnabled: dispatch?.request.productChangeEnabled === true, ...(dispatch ? { worker: dispatch.request.worker, workflowRevision: env.GITHUB_SHA, dispatch: { commentId: dispatch.commentId, requestId: dispatch.request.id, digest: dispatch.receipt.digest, routerRunId: dispatch.request.routerRunId, routerAttempt: dispatch.request.routerAttempt } } : {}), ...(productWorker ? { authorizationDigest, policyDigest, productChangeName, baselineDigest, productEntry: dispatch.request.productFeedback ? 'feedback' : dispatch.request.productParent ? 'parent-handoff' : 'idea', ...((dispatch.request.productParent ?? dispatch.request.productReview?.parent ?? continuation?.request.productParent) ? { productParent: dispatch.request.productParent ?? dispatch.request.productReview?.parent ?? continuation.request.productParent } : {}) } : {}), ...(planningReference ? { planningReference } : {}), ...(productResume ? { productResume, baseRevision } : {}), ...(dispatch?.request.productAlignment ? { productAlignment: dispatch.request.productAlignment } : {}), ...(implementationReview ? { implementationReview, baseRevision } : epicImplementation ? { baseRevision } : {}), ...(proposalReview ? { proposalReview, ...(proposalReview.productProposal ? { baseRevision } : {}) } : {}), repository: repo, runId: id, issue: issueNumber, stage, title: issue.title, defaultBranch: repository.default_branch, sourceRevision: revision.sha, taskDigest: taskDigest(issue, answers), workflowRunId: env.GITHUB_RUN_ID, ...(predecessor ? { refinement: predecessor } : {}), ...(continuation ? { replyTo: continuation.request.runId, commentId: continuationAnswer.id, answerHistory: continuationHistory(continuation, continuationAnswer) } : {}), ...(implementation ? { implementation } : {}), ...(seed ? { seedDigest: changesDigest(seed) } : {}) };
|
|
486
489
|
if (stage === 'triage') snapshot.originalRequest = issue.body ?? '';
|
|
487
490
|
if (triage) snapshot.triageEstimate = triage.envelope.result.output.estimate;
|
|
488
491
|
await output('source_revision', revision.sha);
|
|
@@ -492,6 +495,7 @@ async function main(command) {
|
|
|
492
495
|
const contract = fileURLToPath(new URL('./host-contract.mjs', import.meta.url));
|
|
493
496
|
const context = JSON.parse(execFileSync(process.execPath, [contract, 'context', join(dir, 'request.json'), join(dir, 'issue.json')], { encoding: 'utf8' }));
|
|
494
497
|
if (productResume) { context.productResume = productResume; context.contract.rules += ' The approved Product Change was applied to the supplied issue branch. Resume only the original parent stage and its existing authorization. Preserve the applied product files and branch; product approval does not authorize merge or broaden implementation scope.'; }
|
|
498
|
+
if (snapshot.productAlignment) { context.productAlignment = snapshot.productAlignment; context.contract.rules += ' The authenticated Product Change worker found that accepted product intent already covers this request. Resume only the original parent stage and its existing authorization. This observation grants no new product decision, implementation scope or merge authority.'; }
|
|
495
499
|
if (!productWorker && ['triage', 'refine', 'propose', 'implement'].includes(stage)) context.contract.rules += ' If the authorized issue requires an adjustment to accepted product intent, stop with status blocked and return output.productGap with only summary and scope. Treat that observation as context, not approval or scope authority. Do not use a handoff question for a technical product prerequisite. Set nextAction to: Workflow maintainer: ensure the optional Product Change workflow is installed and follow its authenticated handoff. If a product or scope decision itself is missing, ask one explicit owner question instead.';
|
|
496
500
|
if (continuation) {
|
|
497
501
|
context.continuation = continuationContext(continuation, continuationAnswer);
|
|
@@ -617,6 +621,13 @@ async function main(command) {
|
|
|
617
621
|
await summary(sent ? 'Product prerequisite requested; the router will verify the completed parent.' : 'No product prerequisite continuation requested.');
|
|
618
622
|
return;
|
|
619
623
|
}
|
|
624
|
+
if (command === 'continue-product-alignment') {
|
|
625
|
+
await readFile(join(dir, 'published'));
|
|
626
|
+
const envelope = JSON.parse(await readFile(join(dir, 'envelope.json'), 'utf8'));
|
|
627
|
+
const sent = await signalProductAlignment(api, snapshot, envelope);
|
|
628
|
+
await summary(sent ? 'Product alignment continuation requested; the router will verify the completed result.' : 'No product alignment continuation requested.');
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
620
631
|
if (command === 'continue-refinement') {
|
|
621
632
|
await readFile(join(dir, 'published'));
|
|
622
633
|
const envelope = JSON.parse(await readFile(join(dir, 'envelope.json'), 'utf8'));
|
|
@@ -650,7 +661,7 @@ async function main(command) {
|
|
|
650
661
|
}
|
|
651
662
|
};
|
|
652
663
|
await checkEpic();
|
|
653
|
-
if (snapshot.productParent && publicationIssue.labels.some(label => label.name === 'future')) throw Error('Product prerequisite was paused before publication');
|
|
664
|
+
if ((snapshot.productParent || snapshot.productAlignment) && publicationIssue.labels.some(label => label.name === 'future')) throw Error('Product prerequisite was paused before publication');
|
|
654
665
|
const checkFeedback = async () => {
|
|
655
666
|
if (!snapshot.proposalReview) return;
|
|
656
667
|
if (snapshot.productResume) { checkProductResumePullRequest(await api(`pulls/${snapshot.productResume.pullRequest}`), repo, snapshot.productResume); if (digest(JSON.stringify(await api(snapshot.proposalReview.path))) !== snapshot.proposalReview.feedbackDigest) throw Error('Proposal feedback changed'); }
|
|
@@ -696,7 +707,7 @@ async function main(command) {
|
|
|
696
707
|
reviewTarget = true;
|
|
697
708
|
await summary(`### Product Change review\n[PR #${published.pullRequest.number}](${published.pullRequest.html_url})\n\nProvider overlay and OpenSpec validation passed. Waiting for the explicit owner product decision.`);
|
|
698
709
|
} else if (product.outcome === 'aligned') {
|
|
699
|
-
proposalLink = '\n\nAccepted product intent already covers this idea. No Product Change was created; delivery still needs its own authorization.';
|
|
710
|
+
proposalLink = !snapshot.productParent ? '\n\nAccepted product intent already covers this idea. No Product Change was created; delivery still needs its own authorization.' : !snapshot.productParent.pullRequest && !snapshot.productParent.review ? '\n\nAccepted product intent already covers this request. The workflow will verify the original authorization before resuming its stage.' : '\n\nAccepted product intent already covers this request. The workflow maintainer must reconcile the parent continuation; no new delivery is authorized by this result.';
|
|
700
711
|
} else {
|
|
701
712
|
proposalLink = product.question ? `\n\nProduct preparation is waiting for one owner decision: ${product.question}` : `\n\nProduct preparation is blocked: ${product.blocker}`;
|
|
702
713
|
}
|
|
@@ -769,7 +780,7 @@ async function main(command) {
|
|
|
769
780
|
const projection = resultHandoffState({ status: value.result.status, stage: snapshot.stage, question, reviewTarget, draft: !!prStateProof });
|
|
770
781
|
const isReview = projection.review;
|
|
771
782
|
const productRecovery = snapshot.worker === 'product-change' ? value.result.output?.productChange?.recovery : null;
|
|
772
|
-
const productGap = value.result.status === 'blocked' && snapshot.worker !== 'product-change' ? productGapNextAction({ gap: value.result.output?.productGap, enabled: snapshot.productChangeEnabled === true, dispatched: !!snapshot.dispatch, paused: publicationIssue.labels.some(label => label.name === 'future') }) : null;
|
|
783
|
+
const productGap = value.result.status === 'blocked' && snapshot.worker !== 'product-change' ? snapshot.productAlignment && value.result.output?.productGap ? { action: 'Workflow maintainer: compare the new product gap with the recorded alignment and determine the next authorized action.', next: 'Automatic continuation has stopped; no repeated product handoff was requested.' } : productGapNextAction({ gap: value.result.output?.productGap, enabled: snapshot.productChangeEnabled === true, dispatched: !!snapshot.dispatch, paused: publicationIssue.labels.some(label => label.name === 'future') }) : null;
|
|
773
784
|
const action = question ? `Owner: ${question} Reply here.` : isReview ? snapshot.worker === 'product-change' ? 'Owner: review the linked Product Change PR and approve the proposal or request changes there.' : 'Owner: open the linked result and record your decision there.' : value.result.status === 'blocked' ? productRecovery ? `Workflow maintainer: ${productRecovery}` : productGap?.action ?? visibleNextAction(value.result.nextAction) : refinementAction?.action ?? 'None.';
|
|
774
785
|
const whatNext = question ? `The answer will be checked against this question before ${snapshot.stage} continues.` : isReview ? 'The workflow waits for the required decision before continuing.' : value.result.status === 'blocked' ? productGap?.next ?? 'Work remains stopped until the blocker is resolved and continuation is authorized.' : refinementAction?.next ?? 'Any continuation follows the configured authorization.';
|
|
775
786
|
const happened = productPublicationMessage({ worker: snapshot.worker, outcome: value.result.output?.productChange?.outcome, reviewTarget, agentMessage: value.effect.body });
|
package/src/stage-dispatch.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export const stagePath = stage => { if (!workers.includes(stage)) throw Error('U
|
|
|
11
11
|
export const implementPath = stagePath('implement');
|
|
12
12
|
export const statusText = (stage, status) => `${({ requested: 'Requested', running: 'Queued', completed: 'Stage completed', waiting: 'Waiting for your answer', blocked: 'Blocked', failed: 'Execution failed', cancelled: 'Cancelled', unknown: 'Execution status unknown' })[status] ?? 'Execution status unknown'} · ${stage}`;
|
|
13
13
|
const positive = value => /^[1-9][0-9]*$/.test(String(value));
|
|
14
|
-
export const isBotContinuation = (event, eventName, actor, triggeringActor) => eventName === 'workflow_dispatch' && actor === 'github-actions[bot]' && triggeringActor === actor && (event.inputs?.stage === 'implement' && event.inputs?.workflow !== 'product-change' || ['triage', 'refine', 'propose', 'implement'].includes(event.inputs?.stage) && ['product-change', 'resume-product'].includes(event.inputs?.workflow)) && positive(event.inputs?.issue) && positive(event.inputs?.after_run);
|
|
14
|
+
export const isBotContinuation = (event, eventName, actor, triggeringActor) => eventName === 'workflow_dispatch' && actor === 'github-actions[bot]' && triggeringActor === actor && (event.inputs?.stage === 'implement' && event.inputs?.workflow !== 'product-change' || ['triage', 'refine', 'propose', 'implement'].includes(event.inputs?.stage) && ['product-change', 'resume-product', 'resume-aligned'].includes(event.inputs?.workflow)) && positive(event.inputs?.issue) && positive(event.inputs?.after_run);
|
|
15
15
|
export const isBotEpicTriage = (event, eventName, actor, triggeringActor) => eventName === 'workflow_dispatch' && actor === 'github-actions[bot]' && triggeringActor === actor && event.inputs?.stage === 'triage' && positive(event.inputs?.issue) && positive(event.inputs?.epic_parent) && positive(event.inputs?.epic_run);
|
|
16
16
|
const sha = value => /^[a-f0-9]{40}$/.test(value ?? '');
|
|
17
17
|
const hash = value => /^[a-f0-9]{64}$/.test(value ?? '');
|
|
@@ -128,7 +128,7 @@ export function verifyDispatchOrigin(request, receipt, parent, worker, repositor
|
|
|
128
128
|
const recovery = !!request.recovery;
|
|
129
129
|
const originPath = recovery ? '.github/workflows/lighthouse-worker-maintenance.yml' : routerPath;
|
|
130
130
|
const originEvent = recovery ? request.routerEventName : request.eventName;
|
|
131
|
-
const continuation = !recovery && isBotContinuation(request.event, parent.event, parent.actor?.login, parent.triggering_actor?.login) && Number(request.event.inputs.issue) === issue && (request.stage === 'implement' && request.handoff && !request.worker && !request.feedbackKey && request.predecessor?.runId === request.event.inputs.after_run || request.worker === 'product-change' && request.productParent?.predecessor?.runId === request.event.inputs.after_run || (!request.worker || request.worker === 'apply-review' && request.productResume?.parent.review) && request.event.inputs.workflow === 'resume-product' && request.productResume?.applyRun?.runId === request.event.inputs.after_run);
|
|
131
|
+
const continuation = !recovery && isBotContinuation(request.event, parent.event, parent.actor?.login, parent.triggering_actor?.login) && Number(request.event.inputs.issue) === issue && (request.stage === 'implement' && request.handoff && !request.worker && !request.feedbackKey && request.predecessor?.runId === request.event.inputs.after_run || request.worker === 'product-change' && request.productParent?.predecessor?.runId === request.event.inputs.after_run || (!request.worker || request.worker === 'apply-review' && request.productResume?.parent.review) && request.event.inputs.workflow === 'resume-product' && request.productResume?.applyRun?.runId === request.event.inputs.after_run || !request.worker && request.event.inputs.workflow === 'resume-aligned' && request.productAlignment?.alignmentRun?.runId === request.event.inputs.after_run);
|
|
132
132
|
const epicTriage = !recovery && isBotEpicTriage(request.event, parent.event, parent.actor?.login, parent.triggering_actor?.login) && request.stage === 'triage' && !request.handoff && !request.worker && Number(request.event.inputs.issue) === issue && Number(request.event.inputs.epic_parent) === request.epic?.parent;
|
|
133
133
|
if (epicTriage ? request.epicKey !== epicTriageKey(repository, request.epic.parent, issue, request.epic.planDigest) || receipt.epicKey !== request.epicKey : request.epicKey !== undefined || receipt.epicKey !== undefined) throw Error('Epic triage identity is invalid');
|
|
134
134
|
if (String(parent.id) !== String(receipt.routerRunId) || parent.run_attempt !== receipt.routerAttempt || request.routerRunId !== receipt.routerRunId || request.routerAttempt !== receipt.routerAttempt || parent.path !== originPath || (parent.event !== 'workflow_run' && !continuation && !epicTriage && (parent.actor?.login !== owner || parent.triggering_actor?.login !== owner)) || !(recovery ? ['schedule', 'workflow_dispatch'] : events).includes(parent.event) || parent.event !== originEvent || parent.head_sha !== (request.routerRevision ?? request.workflowRevision) || parent.head_branch !== (request.routerRef ?? request.ref) || parent.repository?.full_name !== repository) throw Error('Dispatch origin is not an authorized router execution');
|
|
@@ -146,11 +146,16 @@ export async function loadDispatch(api, repository, owner, issue, commentId, wor
|
|
|
146
146
|
const parent = await api(`actions/runs/${receipt.routerRunId}/attempts/${receipt.routerAttempt}`);
|
|
147
147
|
const request = await readArtifact(api, repository, receipt.routerRunId, dispatchArtifact(receipt), 'dispatch.json');
|
|
148
148
|
verifyDispatchOrigin(request, receipt, parent, worker, repository, owner, issue);
|
|
149
|
-
if (isBotContinuation(request.event, parent.event, parent.actor?.login, parent.triggering_actor?.login) && !request.productParent && request.event?.inputs?.workflow
|
|
149
|
+
if (isBotContinuation(request.event, parent.event, parent.actor?.login, parent.triggering_actor?.login) && !request.productParent && !['resume-product', 'resume-aligned'].includes(request.event?.inputs?.workflow)) {
|
|
150
150
|
const predecessor = await api(`actions/runs/${request.predecessor.runId}`);
|
|
151
151
|
const next = await (await import('./stage-transition.mjs')).refinementAfterRun(api, repository, owner, { action: 'completed', workflow_run: { id: predecessor.id, head_sha: predecessor.head_sha } }, readArtifact);
|
|
152
152
|
if (next?.issue !== issue || JSON.stringify(next.predecessor) !== JSON.stringify(request.predecessor)) throw Error('Bot continuation lacks an authenticated refinement');
|
|
153
153
|
}
|
|
154
|
+
if (request.productAlignment) {
|
|
155
|
+
const run = await api(`actions/runs/${request.productAlignment.alignmentRun.runId}`);
|
|
156
|
+
const observed = await (await import('./stage-transition.mjs')).productAlignmentAfterRun(api, repository, owner, { action: 'completed', workflow_run: { id: run.id, head_sha: run.head_sha } }, readArtifact);
|
|
157
|
+
if (JSON.stringify(observed) !== JSON.stringify(request.productAlignment) || observed.baseRevision !== request.sourceRevision || (observed.parent.feedback?.taskDigest ?? observed.parent.taskDigest) !== request.taskDigest || observed.stage !== request.stage) throw Error('Product alignment or parent authorization changed');
|
|
158
|
+
}
|
|
154
159
|
if (request.productResume) {
|
|
155
160
|
const apply = await api(`actions/runs/${request.productResume.applyRun.runId}`);
|
|
156
161
|
const observed = await (await import('./stage-transition.mjs')).productApplyAfterRun(api, repository, owner, { action: 'completed', workflow_run: { id: apply.id, head_sha: apply.head_sha } }, readArtifact);
|
|
@@ -221,7 +226,7 @@ export async function completedEvent(api, event, env, wait = ms => new Promise(r
|
|
|
221
226
|
if (!bot && (env.GITHUB_EVENT_NAME !== 'workflow_dispatch' || env.GITHUB_ACTOR !== env.GITHUB_REPOSITORY_OWNER || env.GITHUB_TRIGGERING_ACTOR !== env.GITHUB_REPOSITORY_OWNER || !positive(id))) throw Error('Completion replay requires an owner and a run ID, or a verified refinement continuation');
|
|
222
227
|
let run = await api(`actions/runs/${id}`);
|
|
223
228
|
if (bot) {
|
|
224
|
-
if (event.inputs?.workflow === 'resume-product' ? run.path !== stagePath('apply-product-change') : event.inputs?.workflow === 'product-change' ? !['triage', 'refine', 'propose', 'implement', 'apply-review'].some(stage => run.path === stagePath(stage)) : run.path !== stagePath('refine')) throw Error(event.inputs?.workflow === 'resume-product' ? 'Bot continuation must reference a product apply' : event.inputs?.workflow === 'product-change' ? 'Bot continuation must reference a supported parent execution' : 'Bot continuation must reference a refinement');
|
|
229
|
+
if (event.inputs?.workflow === 'resume-product' ? run.path !== stagePath('apply-product-change') : event.inputs?.workflow === 'resume-aligned' ? run.path !== stagePath('product-change') : event.inputs?.workflow === 'product-change' ? !['triage', 'refine', 'propose', 'implement', 'apply-review'].some(stage => run.path === stagePath(stage)) : run.path !== stagePath('refine')) throw Error(event.inputs?.workflow === 'resume-product' ? 'Bot continuation must reference a product apply' : event.inputs?.workflow === 'resume-aligned' ? 'Bot continuation must reference a product alignment' : event.inputs?.workflow === 'product-change' ? 'Bot continuation must reference a supported parent execution' : 'Bot continuation must reference a refinement');
|
|
225
230
|
// The publisher signals before its own workflow has finished its final jobs.
|
|
226
231
|
for (let attempt = 0; run.status !== 'completed' && attempt < 24; attempt++) {
|
|
227
232
|
await wait(5000); run = await api(`actions/runs/${id}`);
|
package/src/stage-transition.mjs
CHANGED
|
@@ -16,7 +16,7 @@ export async function signalRefinementContinuation(api, request, envelope) {
|
|
|
16
16
|
|
|
17
17
|
// Signal only after publication and cleanup; the router authenticates the completed run.
|
|
18
18
|
export async function signalProductGap(api, request, envelope) {
|
|
19
|
-
if (!request.dispatch || !request.productChangeEnabled || !['triage', 'refine', 'propose', 'implement'].includes(request.stage) || envelope.result?.status !== 'blocked' || !envelope.result.output?.productGap) return false;
|
|
19
|
+
if (!request.dispatch || request.productAlignment || !request.productChangeEnabled || !['triage', 'refine', 'propose', 'implement'].includes(request.stage) || envelope.result?.status !== 'blocked' || !envelope.result.output?.productGap) return false;
|
|
20
20
|
if (validateStageEnvelope(envelope, request).length) throw Error('Invalid product prerequisite result');
|
|
21
21
|
const repository = await api('');
|
|
22
22
|
await api('actions/workflows/lighthouse-sdlc-router.yml/dispatches', 'POST', { ref: repository.default_branch, inputs: { issue: String(request.issue), stage: request.stage, workflow: 'product-change', after_run: String(request.workflowRunId) } });
|
|
@@ -30,6 +30,14 @@ export async function signalProductApplyContinuation(api, issue, runId, checkpoi
|
|
|
30
30
|
return true;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
export async function signalProductAlignment(api, request, envelope) {
|
|
34
|
+
if (request.worker !== 'product-change' || !request.dispatch || !request.productParent || request.productParent.pullRequest || request.productParent.review || envelope.result?.status !== 'passed' || envelope.result.output?.productChange?.outcome !== 'aligned') return false;
|
|
35
|
+
if (validateStageEnvelope(envelope, request).length) throw Error('Invalid product alignment result');
|
|
36
|
+
const repository = await api('');
|
|
37
|
+
await api('actions/workflows/lighthouse-sdlc-router.yml/dispatches', 'POST', { ref: repository.default_branch, inputs: { issue: String(request.issue), stage: request.productParent.stage, workflow: 'resume-aligned', after_run: String(request.workflowRunId) } });
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
33
41
|
// The completed execution is an observation. Its retained owner request grants the handoff.
|
|
34
42
|
export async function refinementAfterRun(api, repository, owner, event, readArtifact = readActionsJson) {
|
|
35
43
|
if (event.action !== 'completed' || !Number.isSafeInteger(event.workflow_run?.id)) return null;
|
|
@@ -108,7 +116,7 @@ export async function productGapAfterRun(api, repository, owner, event, readArti
|
|
|
108
116
|
|
|
109
117
|
// Recheck the original parent and, when present, the authenticated answer to a
|
|
110
118
|
// product question. The answer changes context; it does not approve product intent.
|
|
111
|
-
export async function verifyProductParent(api, repository, owner, parent, issue, reports, currentBase, readArtifact = readActionsJson) {
|
|
119
|
+
export async function verifyProductParent(api, repository, owner, parent, issue, reports, currentBase, readArtifact = readActionsJson, allowAligned = false) {
|
|
112
120
|
const { feedback, ...original } = parent;
|
|
113
121
|
const parentRun = await api(`actions/runs/${original.predecessor.runId}`);
|
|
114
122
|
const observed = await productGapAfterRun(api, repository, owner, { action: 'completed', workflow_run: { id: parentRun.id, head_sha: parentRun.head_sha } }, readArtifact);
|
|
@@ -119,7 +127,7 @@ export async function verifyProductParent(api, repository, owner, parent, issue,
|
|
|
119
127
|
if (run.path !== stagePath('product-change') || run.status !== 'completed' || run.conclusion !== 'success' || run.run_attempt !== feedback.attempt) throw Error('Product answer execution is unavailable');
|
|
120
128
|
const snapshot = await readArtifact(api, repository, run.id, `lighthouse-input-${issue.number}-${run.run_attempt}`, 'request.json');
|
|
121
129
|
const envelope = await readArtifact(api, repository, run.id, `lighthouse-result-${issue.number}-${run.run_attempt}`, 'envelope.json');
|
|
122
|
-
if (!snapshot.dispatch || snapshot.issue !== issue.number || String(snapshot.workflowRunId) !== String(run.id) || snapshot.taskDigest !== feedback.taskDigest || snapshot.replyTo !== feedback.replyTo || snapshot.commentId !== feedback.commentId || JSON.stringify(snapshot.productParent) !== JSON.stringify(original) || validateStageEnvelope(envelope, snapshot).length || envelope.result.status !== 'passed' || envelope.result.output?.productChange?.outcome !== 'candidate') throw Error('Product answer execution does not bind the parent and
|
|
130
|
+
if (!snapshot.dispatch || snapshot.issue !== issue.number || String(snapshot.workflowRunId) !== String(run.id) || snapshot.taskDigest !== feedback.taskDigest || snapshot.replyTo !== feedback.replyTo || snapshot.commentId !== feedback.commentId || JSON.stringify(snapshot.productParent) !== JSON.stringify(original) || validateStageEnvelope(envelope, snapshot).length || envelope.result.status !== 'passed' || envelope.result.output?.productChange?.outcome !== 'candidate' && !(allowAligned && envelope.result.output?.productChange?.outcome === 'aligned')) throw Error('Product answer execution does not bind the parent and outcome');
|
|
123
131
|
const origin = await loadDispatch(api, repository, owner, issue.number, snapshot.dispatch.commentId, run, readArtifact);
|
|
124
132
|
await checkDispatchClaim(api, repository, issue.number, snapshot.dispatch, run.id);
|
|
125
133
|
const answer = origin.request.event?.comment;
|
|
@@ -146,6 +154,32 @@ export async function verifyProductParent(api, repository, owner, parent, issue,
|
|
|
146
154
|
return true;
|
|
147
155
|
}
|
|
148
156
|
|
|
157
|
+
export async function productAlignmentAfterRun(api, repository, owner, event, readArtifact = readActionsJson) {
|
|
158
|
+
if (event.action !== 'completed' || !Number.isSafeInteger(event.workflow_run?.id)) return null;
|
|
159
|
+
const run = await api(`actions/runs/${event.workflow_run.id}`);
|
|
160
|
+
if (run.path !== stagePath('product-change') || run.status !== 'completed' || run.conclusion !== 'success' || run.head_sha !== event.workflow_run.head_sha) return null;
|
|
161
|
+
const artifacts = await api(`actions/runs/${run.id}/artifacts?per_page=100`);
|
|
162
|
+
if (artifacts.total_count > 100) throw Error('Product alignment artifacts exceed bounds');
|
|
163
|
+
const inputs = artifacts.artifacts.filter(a => /^lighthouse-input-[1-9][0-9]*-[1-9][0-9]*$/.test(a.name) && a.name.endsWith(`-${run.run_attempt}`) && !a.expired);
|
|
164
|
+
if (inputs.length !== 1) return null;
|
|
165
|
+
const snapshot = await readArtifact(api, repository, run.id, inputs[0].name, 'request.json');
|
|
166
|
+
if (snapshot.worker !== 'product-change' || !snapshot.dispatch || !snapshot.productParent || inputs[0].name !== `lighthouse-input-${snapshot.issue}-${run.run_attempt}` || String(snapshot.workflowRunId) !== String(run.id)) return null;
|
|
167
|
+
const envelope = await readArtifact(api, repository, run.id, `lighthouse-result-${snapshot.issue}-${run.run_attempt}`, 'envelope.json');
|
|
168
|
+
if (validateStageEnvelope(envelope, snapshot).length) throw Error('Invalid product alignment result');
|
|
169
|
+
if (envelope.result.status !== 'passed' || envelope.result.output?.productChange?.outcome !== 'aligned' || envelope.result.output.productChange.entry !== (snapshot.replyTo ? 'feedback' : 'parent-handoff')) return null;
|
|
170
|
+
const origin = await loadDispatch(api, repository, owner, snapshot.issue, snapshot.dispatch.commentId, run, readArtifact);
|
|
171
|
+
if (origin.request.worker !== 'product-change' || origin.request.id !== snapshot.dispatch.requestId || origin.request.stage !== snapshot.stage || origin.request.sourceRevision !== snapshot.sourceRevision || origin.request.workflowRevision !== snapshot.workflowRevision || !snapshot.replyTo && JSON.stringify(origin.request.productParent) !== JSON.stringify(snapshot.productParent) || origin.request.taskDigest !== snapshot.taskDigest) throw Error('Product alignment authority changed');
|
|
172
|
+
await checkDispatchClaim(api, repository, snapshot.issue, snapshot.dispatch, run.id);
|
|
173
|
+
const parent = snapshot.replyTo ? { ...snapshot.productParent, feedback: { runId: String(run.id), attempt: run.run_attempt, taskDigest: snapshot.taskDigest, replyTo: snapshot.replyTo, commentId: snapshot.commentId } } : snapshot.productParent;
|
|
174
|
+
if (parent.pullRequest || parent.review) return null;
|
|
175
|
+
const reports = await issueComments(api, snapshot.issue);
|
|
176
|
+
if (reports.filter(c => c.user?.login === 'github-actions[bot]' && c.user.type === 'Bot' && c.body.includes(`<!-- lighthouse-actions:${snapshot.runId} -->`) && c.body.includes(`<!-- lighthouse-stage:${snapshot.stage}:passed -->`)).length !== 1) throw Error('Product alignment publication is unavailable or ambiguous');
|
|
177
|
+
const repo = await api(''), baseRevision = (await api(`commits/${encodeURIComponent(repo.default_branch)}`)).sha, issue = await api(`issues/${snapshot.issue}`);
|
|
178
|
+
await verifyProductParent(api, repository, owner, parent, issue, reports, baseRevision, readArtifact, true);
|
|
179
|
+
if (issue.state !== 'open' || issue.labels?.some(l => ['future', 'needs-info', 'waiting-review', 'human-working', 'agent-working'].includes(l.name))) throw Error('Product alignment continuation is paused');
|
|
180
|
+
return { issue: snapshot.issue, stage: parent.stage, parent, alignmentRun: { runId: String(run.id), attempt: run.run_attempt }, baseRevision };
|
|
181
|
+
}
|
|
182
|
+
|
|
149
183
|
// A product apply is usable by a parent only when its original approval, provider
|
|
150
184
|
// result, published receipt, current PR head and retained parent still agree.
|
|
151
185
|
export async function productApplyAfterRun(api, repository, owner, event, readArtifact = readActionsJson) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"
|
|
1
|
+
# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"f6a5fb2aaa8a8089b83cd24aa185fe64db6c76b4069615e4b7810da5c83ae8d7","body_hash":"5269ffd1955c7c2f756025111209f18fd1d97a635086c7d29d7a24d1b8a9b09d","compiler_version":"v0.93.2","agent_id":"opencode","engine_versions":{"opencode":"2.1.0"},"agent_image_runner":"[\"self-hosted\",\"linux\",\"lighthouse-workflow\"]"}
|
|
2
2
|
# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_DEFAULT_OTLP_ENDPOINT","GH_AW_DEFAULT_OTLP_HEADERS","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/checkout","sha":"fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09","version":"v5"},{"repo":"actions/download-artifact","sha":"9000827ccba6bdab643e8b6fd33ac0654aef8333","version":"v8.0.2"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"949feb2413d6458794dcd2491c4babbbce0c15c1","version":"v7.1.0"},{"repo":"actions/setup-node","sha":"a0853c24544627f65ddf259abe73b1d18a591444","version":"v5"},{"repo":"actions/upload-artifact","sha":"cf430e030ddbb5b0abf93d22962f4752f3646cd9","version":"v7.0.2"},{"repo":"github/gh-aw-actions/setup","sha":"d9128eb3ea40c18b77c76184eda03c0ff2608d79","version":"v0.93.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:41835613992df88f51e923bad486f96dfb2e6cda29f13e217df41833284ae382","pinned_image":"ghcr.io/github/gh-aw-node@sha256:41835613992df88f51e923bad486f96dfb2e6cda29f13e217df41833284ae382"}],"mcp_servers":[{"name":"safeoutputs","tools":["noop"]}],"threat_detection":{"mode":"disabled"},"admission":false}
|
|
3
3
|
# This file was automatically generated by gh-aw (v0.93.2). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
|
|
4
4
|
#
|
|
@@ -1362,3 +1362,8 @@ jobs:
|
|
|
1362
1362
|
LIGHTHOUSE_AGENT_RESULT: ${{ needs.agent.result }}
|
|
1363
1363
|
LIGHTHOUSE_PRE_ACTIVATION_RESULT: ${{ needs.pre_activation.result }}
|
|
1364
1364
|
LIGHTHOUSE_PUBLISH_STATUS: ${{ job.status }}
|
|
1365
|
+
- name: Continue an aligned parent request
|
|
1366
|
+
if: success() && needs.agent.result == 'success'
|
|
1367
|
+
run: node .lighthouse-harness/src/packages/sdlc/src/issue-actions.mjs continue-product-alignment
|
|
1368
|
+
env:
|
|
1369
|
+
GH_TOKEN: ${{ github.token }}
|
|
@@ -217,6 +217,11 @@ jobs:
|
|
|
217
217
|
LIGHTHOUSE_AGENT_RESULT: ${{ needs.agent.result }}
|
|
218
218
|
LIGHTHOUSE_PUBLISH_STATUS: ${{ job.status }}
|
|
219
219
|
run: node .lighthouse-harness/src/packages/sdlc/src/issue-actions.mjs cleanup
|
|
220
|
+
- name: Continue an aligned parent request
|
|
221
|
+
if: success() && needs.agent.result == 'success'
|
|
222
|
+
env:
|
|
223
|
+
GH_TOKEN: ${{ github.token }}
|
|
224
|
+
run: node .lighthouse-harness/src/packages/sdlc/src/issue-actions.mjs continue-product-alignment
|
|
220
225
|
---
|
|
221
226
|
|
|
222
227
|
# Prepare a product change from an idea
|
|
@@ -34,7 +34,7 @@ on:
|
|
|
34
34
|
required: true
|
|
35
35
|
default: stage
|
|
36
36
|
type: choice
|
|
37
|
-
options: [stage, product-change, resume-product]
|
|
37
|
+
options: [stage, product-change, resume-product, resume-aligned]
|
|
38
38
|
model_selection:
|
|
39
39
|
description: Optional JSON model selection for this execution
|
|
40
40
|
required: false
|