@event4u/agent-config 14.14.0 → 14.15.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.
Files changed (72) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +63 -0
  5. package/README.md +1 -1
  6. package/dist/agent-src/commands/pr/create.md +16 -1
  7. package/dist/agent-src/contexts/execution/terminal-states.md +25 -4
  8. package/dist/agent-src/rules/missing-skill-recovery.md +2 -2
  9. package/dist/agent-src/rules/token-budget-discipline.md +2 -2
  10. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  11. package/dist/cli/registry.js +1 -0
  12. package/dist/cli-delegate/{chunk-ILMUPLGW.js → chunk-7RXIKF62.js} +159 -86
  13. package/dist/cli-delegate/cmd_conformance.js +513 -17
  14. package/dist/cli-delegate/cmd_doctor.js +1 -1
  15. package/dist/discovery/deprecation-report.md +1 -1
  16. package/dist/discovery/discovery-manifest.json +5 -5
  17. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  18. package/dist/discovery/discovery-manifest.summary.md +1 -1
  19. package/dist/discovery/dormancy-report.md +1 -1
  20. package/dist/discovery/orphan-report.md +1 -1
  21. package/dist/discovery/packs.json +2 -2
  22. package/dist/discovery/trust-report.md +1 -1
  23. package/dist/discovery/workspaces.json +2 -2
  24. package/dist/hooks/dispatch.js +1043 -390
  25. package/dist/mcp/registry-manifest.json +2 -2
  26. package/dist/mcp/server.json +2 -2
  27. package/dist/mcp/server.mjs +703 -115
  28. package/package.json +1 -1
  29. package/src/config/assurance-capability-registry.json +1 -1
  30. package/src/config/evaluator-budgets.json +4 -4
  31. package/src/config/gate-coverage.yml +62 -2
  32. package/src/config/gate-reachability-exemptions.json +1 -0
  33. package/src/config/gate-violation-baselines.json +4 -3
  34. package/src/config/rule-enforcement-baseline.json +0 -1
  35. package/src/config/web-launch-readiness.json +4 -2
  36. package/src/scripts/_cli/_doctor_emit.ts +71 -0
  37. package/src/scripts/_cli/_doctor_strict.ts +163 -0
  38. package/src/scripts/_cli/cmd_doctor.ts +16 -37
  39. package/src/scripts/_dispatch.bash +11 -0
  40. package/src/scripts/_lib/branch_convergence.ts +186 -0
  41. package/src/scripts/_lib/context_observation.ts +132 -0
  42. package/src/scripts/_lib/continuation_ladder.ts +188 -0
  43. package/src/scripts/_lib/experiment_binding.ts +95 -0
  44. package/src/scripts/_lib/ignored_blocker.ts +8 -1
  45. package/src/scripts/_lib/outcome_envelope.ts +6 -1
  46. package/src/scripts/_lib/outcome_vocabularies.ts +135 -5
  47. package/src/scripts/_lib/release_highlights.ts +75 -10
  48. package/src/scripts/_lib/repeated_failure.ts +19 -2
  49. package/src/scripts/_lib/runtime_journal.ts +11 -4
  50. package/src/scripts/check_agent_artifact_location.ts +337 -0
  51. package/src/scripts/check_artefact_count_messaging.ts +74 -8
  52. package/src/scripts/check_enforcement_coverage.ts +40 -1
  53. package/src/scripts/check_release_highlights.ts +17 -1
  54. package/src/scripts/check_web_launch_readiness.ts +310 -20
  55. package/src/scripts/evolution_lab.ts +53 -1
  56. package/src/scripts/generate_capability_matrix.ts +16 -9
  57. package/src/scripts/git_authorization_hook.ts +476 -6
  58. package/src/scripts/grade_target_readiness.ts +221 -2
  59. package/src/scripts/hook_effect_doctor.ts +47 -4
  60. package/src/scripts/hooks/block_unauthorized_git.ts +238 -9
  61. package/src/scripts/hooks/merge_impact.ts +207 -0
  62. package/src/scripts/hooks/run_continuation_hook.ts +78 -88
  63. package/src/scripts/hooks/session_eol_hook.ts +12 -1
  64. package/src/scripts/install-hooks.sh +18 -0
  65. package/src/scripts/lint_code_comments.ts +106 -1
  66. package/src/scripts/release.ts +5 -17
  67. package/src/scripts/release_drill.ts +76 -1
  68. package/src/scripts/release_env.ts +31 -0
  69. package/src/scripts/release_publication.ts +125 -1
  70. package/src/scripts/roadmap_context.ts +15 -0
  71. package/src/scripts/skill_scout.ts +633 -0
  72. package/src/scripts/sync_pr_branch.ts +521 -42
@@ -122,6 +122,20 @@ interface WorldConfig {
122
122
  * its own, because a marker otherwise stops the run at step 8.
123
123
  */
124
124
  tag_already_remote?: boolean;
125
+ /**
126
+ * The release PR is ALREADY merged when the run starts — the real
127
+ * resume-after-merge state, in which steps 1 through 7 skip and step 8 is
128
+ * the first step that does work.
129
+ *
130
+ * Added 2026-09-02 with `guard_release_branch_push`. Before that guard, a
131
+ * marker scenario could start from scratch and still reach step 8, because
132
+ * nothing between the changelog write and the tag looked at the section.
133
+ * The push guard now stops such a run at step 4 — correctly, and that is
134
+ * the point of it — which makes this knob the only way left to exercise
135
+ * the tag and Release guards on their own. It is not a bypass: the state
136
+ * it fakes is one every resumed release actually passes through.
137
+ */
138
+ pr_already_merged?: boolean;
125
139
  }
126
140
 
127
141
  /**
@@ -206,6 +220,7 @@ class FakeWorld {
206
220
  this.merge_races_once = cfg.merge_races_once ?? false;
207
221
  this.merge_fails_hard = cfg.merge_fails_hard ?? false;
208
222
  this.checks_fail = cfg.checks_fail ?? false;
223
+ this.pr_merged = cfg.pr_already_merged ?? false;
209
224
  }
210
225
 
211
226
  exec(args: readonly string[]): ExecResult {
@@ -375,10 +390,68 @@ function _count(world: FakeWorld, needle: string): number {
375
390
  }
376
391
 
377
392
  const SCENARIOS: Record<string, Scenario> = {
393
+ 'marker-refuses-before-commit': {
394
+ summary:
395
+ 'step 3: a draft marker refuses BEFORE the release commit — no local release state, and therefore no remote state either',
396
+ config: { changelog_file: markedChangelogFixture(current_version()) },
397
+ expect_success: false,
398
+ verify: (w, err) => {
399
+ const f: string[] = [];
400
+ _expect(
401
+ (err ?? '').includes('release highlights are still the generator'),
402
+ `died for the wrong reason: ${err ?? '(no error)'}`,
403
+ f,
404
+ );
405
+ _expect(
406
+ !w.calls.some((c) => c.startsWith('git commit')),
407
+ 'a release commit was made after the refusal',
408
+ f,
409
+ );
410
+ // RENAMED from `marker-refuses-before-branch-push` when
411
+ // `guard_release_curation` moved the refusal one step earlier.
412
+ // Every negative assertion below is UNCHANGED and still
413
+ // load-bearing — each is a remote side effect the 14.14.0 run
414
+ // actually produced: a pushed branch, an open PR, a check watch on
415
+ // a check that could not pass. They now hold more strongly, since
416
+ // the run stops before the commit rather than after it, and the
417
+ // no-commit assertion above is the property that is new.
418
+ //
419
+ // What this scenario no longer reaches, said plainly rather than
420
+ // left for a reader to infer: `guard_release_branch_push`. On the
421
+ // non-merged path it is now unreachable in ONE run, because a
422
+ // section marked at step 4 was already marked at step 3. It stays
423
+ // as defence-in-depth for a section that changes BETWEEN the two —
424
+ // a hand edit mid-run, or a resume over an altered changelog — and
425
+ // its own behaviour stays pinned by the unit tests in
426
+ // `tests/scripts/release_push_failure_masking.test.ts`, which is
427
+ // where that coverage moved to rather than being dropped.
428
+ _expect(
429
+ !w.calls.includes(`git push -u origin ${w.branch}`),
430
+ 'the branch was pushed after the refusal',
431
+ f,
432
+ );
433
+ _expect(
434
+ !w.calls.some((c) => c.startsWith('gh pr create')),
435
+ 'a pull request was opened after the refusal',
436
+ f,
437
+ );
438
+ _expect(
439
+ !w.calls.some((c) => c.includes('--watch')),
440
+ 'CI was waited on after the refusal',
441
+ f,
442
+ );
443
+ _expect(!w.tag_local && !w.tag_remote, 'a tag exists after the refusal', f);
444
+ _expect(!w.release_created, 'a GitHub Release was created after the refusal', f);
445
+ return f;
446
+ },
447
+ },
378
448
  'marker-refuses-before-tag': {
379
449
  summary:
380
450
  'step 8: a draft marker in the merged section refuses BEFORE the annotated tag is created',
381
- config: { changelog_file: markedChangelogFixture(current_version()) },
451
+ config: {
452
+ pr_already_merged: true,
453
+ changelog_file: markedChangelogFixture(current_version()),
454
+ },
382
455
  expect_success: false,
383
456
  verify: (w, err) => {
384
457
  const f: string[] = [];
@@ -403,6 +476,7 @@ const SCENARIOS: Record<string, Scenario> = {
403
476
  summary:
404
477
  'step 8 (§ 3.2): a tag created but never pushed is NOT pushed when its section carries the marker',
405
478
  config: {
479
+ pr_already_merged: true,
406
480
  tag_created_unpushed: true,
407
481
  changelog_file: markedChangelogFixture(current_version()),
408
482
  },
@@ -431,6 +505,7 @@ const SCENARIOS: Record<string, Scenario> = {
431
505
  summary:
432
506
  'step 9: with the tag already pushed, a marker in the TAGGED section refuses before the Release is created',
433
507
  config: {
508
+ pr_already_merged: true,
434
509
  tag_already_remote: true,
435
510
  changelog: markedChangelogFixture(current_version()),
436
511
  changelog_file: markedChangelogFixture(current_version()),
@@ -303,6 +303,35 @@ function set_marketplace_version(p: string, version: string): void {
303
303
  fs.writeFileSync(p, jsonDumpsIndent(data, 2) + '\n', 'utf-8');
304
304
  }
305
305
 
306
+ /**
307
+ * Mirror of Python `str.splitlines()` — split on CR/LF/CRLF and drop a single
308
+ * trailing empty element.
309
+ *
310
+ * Lives here rather than in `release.ts` because that file is 2000+ lines and
311
+ * every line above 1500 is charged by `check_source_size_budget`, while this
312
+ * module is a third of the cap. Behaviour unchanged; it is a pure text helper
313
+ * beside the other pure text helpers in this module.
314
+ *
315
+ * Known duplication, recorded rather than fixed here: `adoption_report.ts:106`
316
+ * and `readme_linter.ts:418` carry their own copies. Collapsing all three onto
317
+ * one definition touches three modules and is not this change's subject.
318
+ */
319
+ function _splitlines(text: string): string[] {
320
+ if (text === '') {
321
+ return [];
322
+ }
323
+ const parts = text.split(/\r\n|\r|\n/);
324
+ if (parts.length > 0 && parts[parts.length - 1] === '') {
325
+ parts.pop();
326
+ }
327
+ return parts;
328
+ }
329
+
330
+ /** Mirror of Python `str.rstrip()` (trailing whitespace). */
331
+ function _rstrip(text: string): string {
332
+ return text.replace(/\s+$/u, '');
333
+ }
334
+
306
335
  export {
307
336
  ArgparseExit,
308
337
  commaGroup,
@@ -330,4 +359,6 @@ export {
330
359
  REPO_ROOT,
331
360
  REPO_SLUG,
332
361
  SystemExitError,
362
+ _rstrip,
363
+ _splitlines,
333
364
  };
@@ -16,7 +16,7 @@
16
16
  * import path changes.
17
17
  */
18
18
  import { spawnSync } from 'node:child_process';
19
- import { publication_blockers } from './_lib/release_highlights.js';
19
+ import { DERIVED_MARKER, publication_blockers } from './_lib/release_highlights.js';
20
20
  import * as fs from 'node:fs';
21
21
  import process from 'node:process';
22
22
 
@@ -422,7 +422,131 @@ export function _is_non_fast_forward(stderr: string, stdout: string): boolean {
422
422
  return /(non-fast-forward|fetch first|behind its remote counterpart)/i.test(text);
423
423
  }
424
424
 
425
+ /**
426
+ * Refuse the FIRST remote state of a release whose section is not publishable.
427
+ *
428
+ * The fourth guard site, and the one the other three structurally cannot cover.
429
+ * They sit at the irreversible transitions — annotated tag, resumed tag push,
430
+ * GitHub Release body — and every one of them is DOWNSTREAM of `gh pr create`
431
+ * and of `gh pr checks --watch`. `check_release_highlights` refuses the same
432
+ * section from the other side, on the release PR, so a run whose head is still
433
+ * the generator's draft pushed a branch, opened a PR, burned CI, waited on a
434
+ * check that could not pass, and died there — leaving a half-open release to
435
+ * clean up by hand.
436
+ *
437
+ * That is not a hypothetical: it is PR #1812 (14.14.0, 2026-09-02), and the
438
+ * 2026-09-01 flip's own risk register pre-registered it as Risk 1 — *"the flip
439
+ * reds the next release PR on its first run"*. Its stated mitigation was that
440
+ * the releaser edits prose before merge; nothing in the pipeline asked, so
441
+ * nothing did.
442
+ *
443
+ * The branch push is the first thing in the pipeline that leaves the machine,
444
+ * so a refusal here costs one local edit and no remote state at all: no branch
445
+ * on the remote, no pull request, no CI minutes, no tag. It sits at the push
446
+ * CALL SITE rather than in a formatter for the reason the publication guards
447
+ * already state — a formatter has no notion of whether it is publishing, and
448
+ * the call site is the one that can still stop. Deriving the version from
449
+ * `release/X.Y.Z` mirrors what `release-validation.yml` already does with
450
+ * `head_ref`.
451
+ *
452
+ * Two states pass unguarded, deliberately: a branch that is not
453
+ * `release/X.Y.Z` (nothing about a release to read), and a version with no
454
+ * changelog section (the missing-section refusal belongs to the publication
455
+ * sites, which need a section to publish; refusing here would break every
456
+ * non-release push through this helper).
457
+ */
458
+ export function guard_release_branch_push(branch: string): void {
459
+ const prefix = 'release/';
460
+ if (!branch.startsWith(prefix)) {
461
+ return;
462
+ }
463
+ const version = branch.slice(prefix.length);
464
+ const section = extract_changelog_section(read_changelog_text(), version);
465
+ if (!section) {
466
+ return;
467
+ }
468
+ const blockers = publication_blockers(section.body, version, `\`${branch}\``);
469
+ if (blockers.length === 0) {
470
+ return;
471
+ }
472
+ die(
473
+ `refusing to push ${branch} — the ${version} changelog section is not publishable ` +
474
+ `(${String(blockers.length)} blocker(s)):\n` +
475
+ blockers.map((b) => ` - ${b}`).join('\n') +
476
+ `\n Nothing has left this machine: no branch on ${REMOTE}, no pull request, no tag.\n` +
477
+ ' Curate the `### Release highlights` head in CHANGELOG.md under ' +
478
+ `\`## [${version}]\`, then re-run \`task release -- --resume\`.`,
479
+ );
480
+ }
481
+
482
+ /**
483
+ * Ask for the curation BEFORE the pipeline commits anything — the fifth guard
484
+ * site, and the one that closes the gap the other four only narrowed.
485
+ *
486
+ * `guard_release_branch_push` already refuses before any REMOTE state exists,
487
+ * which is why 14.14.1 cost nothing but a re-run. What it cannot do is refuse
488
+ * before LOCAL state exists: by the time the push is attempted, step 2 has
489
+ * bumped six files and regenerated the derived trees and step 3 has committed
490
+ * all of it as `release: X.Y.Z`. The operator then edits prose on top of a
491
+ * commit that already claims to be the release and continues with `--resume`.
492
+ *
493
+ * The sentence this fixes is in that guard's own docstring: the 2026-09-01
494
+ * flip's risk register named *"the releaser edits prose before merge"* as the
495
+ * mitigation, and *"nothing in the pipeline asked, so nothing did"*. Refusing
496
+ * is not asking. This is the ask, placed where the answer is cheapest: the
497
+ * changelog section has just been written, nothing is committed, and the fix is
498
+ * an edit to a file already open in the working tree.
499
+ *
500
+ * It names the labels rather than only the marker, because the operator's next
501
+ * action is to rewrite specific lines and hunting for them in a 30-line section
502
+ * is the friction that made the refusal feel like a failure rather than a step.
503
+ *
504
+ * Deliberately silent when the section does not exist — same reasoning as the
505
+ * push guard: a missing section is the publication sites' refusal to make, not
506
+ * this one's.
507
+ *
508
+ * `prMerged` is a parameter rather than a check at the call site, and it is
509
+ * load-bearing in both directions. On the merged path steps 2 and 3 do not run,
510
+ * so there is no pre-commit moment to ask about; firing anyway PREEMPTS the
511
+ * step-8 and step-9 guards, whose whole subject is the merged section. That is
512
+ * measured, not anticipated — the unconditional form red three `release_drill`
513
+ * scenarios that exist to pin exactly those later refusals. It lives here
514
+ * rather than in an `if` at the call site because `release.ts` is 2000+ lines
515
+ * and every line above 1500 is charged by the growth ratchet, while this module
516
+ * is far below it.
517
+ */
518
+ export function guard_release_curation(version: string, prMerged = false): void {
519
+ if (prMerged) {
520
+ return;
521
+ }
522
+ const section = extract_changelog_section(read_changelog_text(), version);
523
+ if (!section) {
524
+ return;
525
+ }
526
+ const blockers = publication_blockers(section.body, version, `\`release/${version}\``);
527
+ if (blockers.length === 0) {
528
+ return;
529
+ }
530
+ const marked = section.body
531
+ .split('\n')
532
+ .filter((l) => l.includes(DERIVED_MARKER))
533
+ .map((l) => ` ${l.trim()}`);
534
+ die(
535
+ `the ${version} release highlights are still the generator's draft ` +
536
+ `(${String(blockers.length)} blocker(s)):\n` +
537
+ blockers.map((b) => ` - ${b}`).join('\n') +
538
+ (marked.length > 0
539
+ ? `\n Lines to rewrite in \`CHANGELOG.md\` under \`## [${version}]\`:\n` +
540
+ marked.join('\n')
541
+ : '') +
542
+ '\n Stopped BEFORE committing — no release commit, no branch on ' +
543
+ `${REMOTE}, no pull request, no tag. Curate the ` +
544
+ '`### Release highlights` head, then re-run `task release`.',
545
+ );
546
+ }
547
+
425
548
  function push_release_branch(branch: string): void {
549
+ guard_release_branch_push(branch);
426
550
  const first = run(['git', 'push', '-u', REMOTE, branch], { check: false, capture: true });
427
551
  if (first.returncode === 0) {
428
552
  process.stdout.write(first.stdout);
@@ -49,6 +49,7 @@ import { fileURLToPath } from 'node:url';
49
49
  import { gitEnv } from './_lib/git_env.js';
50
50
  import { read_live_records, register_dir } from './_lib/session_register.js';
51
51
  import { other_worktree_branches_detailed } from './sessions_cli.js';
52
+ import { recordContextObservation } from './_lib/context_observation.js';
52
53
 
53
54
  /** One open pull request, with the file set it changes. */
54
55
  export interface PullRequestInfo {
@@ -746,6 +747,20 @@ export function main(argv: string[] = process.argv.slice(2)): number {
746
747
  roadmap: at('--roadmap'),
747
748
  ...(ownedFile !== null ? { ownedPaths: readOwnedPathsFile(ownedFile) } : {}),
748
749
  });
750
+ // `road-to-wired-instruments` 2.1 — this probe is the only thing in the tree
751
+ // that can OBSERVE the world (it costs a `gh` call), so it records what it
752
+ // saw where a Stop-path concern can read it without spawning anything. The
753
+ // continuation ladder's premise rung is the consumer; the resume checkpoint
754
+ // is the other.
755
+ //
756
+ // Only a LIVE reading is recorded. Offline, `contextFingerprint` still
757
+ // returns a digest — over a null base and an empty PR list — and that digest
758
+ // is a statement about the network, not about the repository. Recording it
759
+ // would make every offline probe look like a moved premise, which is a false
760
+ // halt manufactured out of a dropped connection.
761
+ if (ctx.network === 'live') {
762
+ recordContextObservation(root, ctx.roadmap ?? '', ctx.fingerprint);
763
+ }
749
764
  if (argv.includes('--fingerprint')) {
750
765
  // Just the digest, for the loop's phase-boundary comparison and for the
751
766
  // resume checkpoint's `context_fingerprint` (§ 5.6 — same value).