@event4u/agent-config 10.4.0 → 11.0.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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +69 -0
- package/dist/agent-src/commands/council/debate.md +13 -4
- package/dist/agent-src/commands/council/default.md +26 -11
- package/dist/agent-src/commands/council.md +1 -1
- package/dist/agent-src/commands/design-system/generate.md +23 -1
- package/dist/agent-src/commands/pr/create.md +51 -26
- package/dist/agent-src/commands/worktree/create.md +5 -3
- package/dist/agent-src/rules/settings-ask-protocol.md +14 -9
- package/dist/agent-src/skills/ai-council/SKILL.md +24 -6
- package/dist/agent-src/skills/corpus-grounding/SKILL.md +12 -1
- package/dist/agent-src/skills/corpus-grounding/scripts/decision_engine.ts +215 -7
- package/dist/agent-src/skills/corpus-grounding/scripts/ground.ts +39 -1
- package/dist/agent-src/skills/design-intelligence/ATTRIBUTION.md +19 -2
- package/dist/agent-src/skills/design-intelligence/data/manifest.json +36 -1
- package/dist/agent-src/skills/design-intelligence/data/motion.csv +17 -0
- package/dist/agent-src/skills/git-workflow/SKILL.md +9 -3
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +9 -6
- package/dist/agent-src/skills/using-git-worktrees/SKILL.md +25 -21
- package/dist/agent-src/templates/agent-settings.md +0 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli-delegate/{chunk-BV3WR67D.js → chunk-55WZN5VF.js} +3 -6
- package/dist/cli-delegate/{chunk-X3VOARZD.js → chunk-L565YFXH.js} +3 -3
- package/dist/cli-delegate/{chunk-JMQZ7MM5.js → chunk-MNWRDMR5.js} +1 -1
- package/dist/cli-delegate/{chunk-IR3IVRVC.js → chunk-NLP54U3J.js} +2 -1
- package/dist/cli-delegate/{chunk-FV34EGEX.js → chunk-NN4KW4HZ.js} +1 -1
- package/dist/cli-delegate/{chunk-6ENSXSBW.js → chunk-PPQO4NGR.js} +1 -1
- package/dist/cli-delegate/{chunk-6LOPHJLD.js → chunk-RYNADUFG.js} +1 -1
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +1 -1
- package/dist/cli-delegate/cmd_conformance.js +2 -2
- package/dist/cli-delegate/cmd_doctor.js +2 -2
- package/dist/cli-delegate/cmd_explain.js +2 -2
- package/dist/cli-delegate/cmd_export.js +4 -4
- package/dist/cli-delegate/cmd_handoff.js +2 -2
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +4 -4
- package/dist/cli-delegate/cmd_route_audit.js +2 -2
- package/dist/cli-delegate/cmd_route_explain.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +1 -1
- package/dist/cli-delegate/cmd_settings_get.js +1 -1
- package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +4 -4
- package/dist/cli-delegate/cmd_uninstall.js +4 -4
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +4 -4
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +14 -14
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +54 -12
- package/dist/install/install.mjs +5 -7
- package/dist/mcp/registry-manifest.json +1 -1
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +2 -1
- package/dist/scripts/_lib/agent_settings.js +8 -0
- package/dist/scripts/_lib/agent_settings.js.map +1 -1
- package/dist/server/schemas/settings.js +3 -4
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/ui/assets/{index-5fXOHoVh.js → index-B0aMJnuz.js} +2 -2
- package/dist/ui/assets/{index-5fXOHoVh.js.map → index-B0aMJnuz.js.map} +1 -1
- package/dist/ui/index.html +1 -1
- package/docs/contracts/settings-classes.md +12 -9
- package/package.json +1 -1
- package/src/config/agent-settings.template.yml +15 -18
- package/src/config/gate-coverage.yml +13 -3
- package/src/config/gate-violation-baselines.json +1 -1
- package/src/config/pack-size-budget.json +3 -2
- package/src/scripts/_lib/agent_settings.ts +8 -0
- package/src/scripts/check_branch_freshness.ts +238 -6
- package/src/scripts/external_sources_denylist.json +7 -2
- package/src/scripts/session_register_hook.ts +120 -10
- package/src/server/schemas/settings.ts +3 -6
|
@@ -531,16 +531,76 @@ function _select_best_match(
|
|
|
531
531
|
|
|
532
532
|
// ── grounding ────────────────────────────────────────────────────────────
|
|
533
533
|
|
|
534
|
+
/**
|
|
535
|
+
* The three optional 1-10 design dials.
|
|
536
|
+
*
|
|
537
|
+
* Adapted from the upstream generator's tier table (MIT; see
|
|
538
|
+
* `design-intelligence/ATTRIBUTION.md` and `provenance/borrows.jsonl`). The
|
|
539
|
+
* TIER BOUNDARIES and the values they carry are the upstream's data; the
|
|
540
|
+
* plumbing below is this engine's own, because upstream threads them through a
|
|
541
|
+
* `generate()` this tree does not have and ours has `ground()` instead.
|
|
542
|
+
*
|
|
543
|
+
* Unset means unset: a dial the caller did not pass changes nothing, which is
|
|
544
|
+
* the upstream's own contract and the only thing that keeps the no-flag path
|
|
545
|
+
* byte-identical to the behaviour before this existed.
|
|
546
|
+
*/
|
|
547
|
+
export const DIAL_TIERS: Record<string, Array<[number, number, ResultDict]>> = {
|
|
548
|
+
variance: [
|
|
549
|
+
[1, 3, { label: 'Centered / Minimal', style_keywords: ['Minimalism', 'Exaggerated Minimalism', 'centered', 'symmetric', 'grid-based'] }],
|
|
550
|
+
[4, 7, { label: 'Balanced / Modern', style_keywords: ['modern', 'structured', 'balanced'] }],
|
|
551
|
+
[8, 10, { label: 'Bold / Asymmetric', style_keywords: ['Brutalism', 'Bento Grids', 'asymmetric', 'experimental'] }],
|
|
552
|
+
],
|
|
553
|
+
motion: [
|
|
554
|
+
[1, 3, { label: 'Subtle', tier: 'Subtle' }],
|
|
555
|
+
[4, 7, { label: 'Standard', tier: 'Standard' }],
|
|
556
|
+
[8, 10, { label: 'Complex', tier: 'Complex' }],
|
|
557
|
+
],
|
|
558
|
+
density: [
|
|
559
|
+
[1, 3, { label: 'Spacious', spacing: { xs: '4px', sm: '8px', md: '24px', lg: '32px', xl: '48px', '2xl': '64px', '3xl': '96px' } }],
|
|
560
|
+
[4, 7, { label: 'Standard', spacing: { xs: '4px', sm: '8px', md: '16px', lg: '24px', xl: '32px', '2xl': '48px', '3xl': '64px' } }],
|
|
561
|
+
[8, 10, { label: 'Dense / Dashboard', spacing: { xs: '2px', sm: '4px', md: '8px', lg: '12px', xl: '16px', '2xl': '24px', '3xl': '32px' } }],
|
|
562
|
+
],
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
/** The dial values a caller may pass; `null`/absent means "do not engage". */
|
|
566
|
+
export interface Dials {
|
|
567
|
+
variance?: number | null;
|
|
568
|
+
motion?: number | null;
|
|
569
|
+
density?: number | null;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Bucket a 1-10 dial value into its tier config, or `null` when unset.
|
|
574
|
+
*
|
|
575
|
+
* Out-of-range input is clamped rather than rejected: the dial is a coarse
|
|
576
|
+
* preference, and refusing an 11 would turn a typo into a failed run for no
|
|
577
|
+
* gain in meaning.
|
|
578
|
+
*/
|
|
579
|
+
export function resolve_dial(dial_name: string, value: number | null | undefined): ResultDict | null {
|
|
580
|
+
if (value === null || value === undefined || Number.isNaN(value)) return null;
|
|
581
|
+
const tiers = DIAL_TIERS[dial_name];
|
|
582
|
+
if (tiers === undefined) return null;
|
|
583
|
+
const v = Math.max(1, Math.min(10, Math.trunc(value)));
|
|
584
|
+
for (const [lo, hi, info] of tiers) {
|
|
585
|
+
if (v >= lo && v <= hi) return { ...info, value: v };
|
|
586
|
+
}
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
|
|
534
590
|
/**
|
|
535
591
|
* Conditional grounding: category → rules → planned multi-domain search.
|
|
536
592
|
*
|
|
537
593
|
* Returns the interface-v1 grounded dict (see Python docstring). Async because
|
|
538
594
|
* `_load_rules_callable` is async (dynamic import).
|
|
595
|
+
*
|
|
596
|
+
* `dials` is optional and additive: with none passed, the returned dict is
|
|
597
|
+
* exactly what it was before dials existed.
|
|
539
598
|
*/
|
|
540
599
|
export async function ground(
|
|
541
600
|
manifest: Manifest,
|
|
542
601
|
query: string,
|
|
543
602
|
context: Record<string, unknown> | null = null,
|
|
603
|
+
dials: Dials | null = null,
|
|
544
604
|
): Promise<ResultDict> {
|
|
545
605
|
const reasoning = manifest.reasoning as Record<string, unknown> | null | undefined;
|
|
546
606
|
if (!reasoning) {
|
|
@@ -607,6 +667,29 @@ export async function ground(
|
|
|
607
667
|
.filter((s) => s.length > 0);
|
|
608
668
|
}
|
|
609
669
|
|
|
670
|
+
// 4b — dials.
|
|
671
|
+
//
|
|
672
|
+
// Variance biases SELECTION, never RETRIEVAL, and never silently.
|
|
673
|
+
//
|
|
674
|
+
// An earlier version prepended the dial's keywords to `priority` outright.
|
|
675
|
+
// That looked like "biasing" and was not: `priority.slice(0, 2)` augments
|
|
676
|
+
// the query, so the rule's own corpus-grounded keywords were pushed out of
|
|
677
|
+
// the retrieval entirely, and five dial keywords then outscored two rule
|
|
678
|
+
// keywords in the match. The dial did not bias the evidence — it replaced
|
|
679
|
+
// it, and nothing said so.
|
|
680
|
+
//
|
|
681
|
+
// Split instead. `priority` stays the rule's, so what comes BACK from the
|
|
682
|
+
// corpus is unchanged by the dial. `selection_priority` carries the dial in
|
|
683
|
+
// front for choosing among those rows — which is what a preference dial is
|
|
684
|
+
// legitimately for. And when that choice differs from the one the rule's own
|
|
685
|
+
// keywords would have made, it is reported rather than left invisible.
|
|
686
|
+
const variance_info = resolve_dial('variance', dials?.variance);
|
|
687
|
+
const motion_info = resolve_dial('motion', dials?.motion);
|
|
688
|
+
const density_info = resolve_dial('density', dials?.density);
|
|
689
|
+
const selection_priority = variance_info
|
|
690
|
+
? [...(variance_info.style_keywords as string[]), ...priority]
|
|
691
|
+
: priority;
|
|
692
|
+
|
|
610
693
|
// 5 — planned multi-domain search.
|
|
611
694
|
const selections: ResultDict = {};
|
|
612
695
|
const domain_confidences: number[] = [];
|
|
@@ -621,12 +704,28 @@ export async function ground(
|
|
|
621
704
|
}
|
|
622
705
|
const result = search_domain(manifest, q, domain_name, _int(max_results));
|
|
623
706
|
const results = (result.results as Row[] | null) || [];
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
707
|
+
let best: Row;
|
|
708
|
+
if (domain_name === priority_domain) {
|
|
709
|
+
best = _select_best_match(results, selection_priority, name_cols[domain_name]);
|
|
710
|
+
if (variance_info) {
|
|
711
|
+
// What the rule alone would have picked from the SAME rows.
|
|
712
|
+
// Reporting the divergence is the whole difference between a
|
|
713
|
+
// dial that biases and a dial that quietly overrules.
|
|
714
|
+
const ungated = _select_best_match(results, priority, name_cols[domain_name]);
|
|
715
|
+
if (ungated !== best) {
|
|
716
|
+
const col = name_cols[domain_name];
|
|
717
|
+
const nameOf = (r: Row): string =>
|
|
718
|
+
col && r[col] !== undefined ? pyStr(r[col]) : '(unnamed)';
|
|
719
|
+
gaps.push(
|
|
720
|
+
`--variance ${String(variance_info.value)} changed the ${domain_name} pick to ` +
|
|
721
|
+
`'${nameOf(best)}'; the rule's own keywords would have selected ` +
|
|
722
|
+
`'${nameOf(ungated)}' — the dial biased the choice, it did not come from the corpus`,
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
} else {
|
|
727
|
+
best = results.length > 0 ? (results[0] as Row) : {};
|
|
728
|
+
}
|
|
630
729
|
selections[domain_name] = {
|
|
631
730
|
best,
|
|
632
731
|
// Python: [r for r in results if r is not best] — identity compare.
|
|
@@ -641,6 +740,53 @@ export async function ground(
|
|
|
641
740
|
domain_confidences.push(score instanceof PyFloat ? score.value : (score as number | undefined) ?? 0.0);
|
|
642
741
|
}
|
|
643
742
|
|
|
743
|
+
// 5b — the motion dial reads a corpus, which is why it needs `motion.csv`
|
|
744
|
+
// and the other two do not. Rows are filtered by the `Intensity Tier`
|
|
745
|
+
// column; a tier with no rows yields an evidence gap rather than a silent
|
|
746
|
+
// empty block, because "the dial did nothing" and "the corpus has nothing
|
|
747
|
+
// at this tier" are different facts to the reader.
|
|
748
|
+
let motion_selection: ResultDict | null = null;
|
|
749
|
+
if (motion_info) {
|
|
750
|
+
const tier = motion_info.tier as string;
|
|
751
|
+
const motion_domain = (manifest.domains as Record<string, ResultDict>)['gsap'];
|
|
752
|
+
if (motion_domain === undefined) {
|
|
753
|
+
gaps.push(
|
|
754
|
+
"the --motion dial needs a 'gsap' search domain (motion.csv); this manifest has none, so the dial had no effect",
|
|
755
|
+
);
|
|
756
|
+
} else {
|
|
757
|
+
// FILTER FIRST, then rank. Retrieving the global top-N and filtering
|
|
758
|
+
// afterwards drops rows at the requested tier that lost the global
|
|
759
|
+
// cut to rows of another tier — the dial then silently returns less
|
|
760
|
+
// than the corpus holds for it. `search_domain` already merges a
|
|
761
|
+
// filters dict (it is what the `--filter` flag uses), and the row
|
|
762
|
+
// count comes from the manifest like every other domain rather than
|
|
763
|
+
// from a literal here.
|
|
764
|
+
const motion_max = motion_domain['max_results'];
|
|
765
|
+
const motion_result = search_domain(
|
|
766
|
+
manifest,
|
|
767
|
+
query,
|
|
768
|
+
'gsap',
|
|
769
|
+
typeof motion_max === 'number' ? motion_max : null,
|
|
770
|
+
{ 'Intensity Tier': tier },
|
|
771
|
+
);
|
|
772
|
+
const rows = (motion_result.results as Row[] | null) || [];
|
|
773
|
+
if (rows.length === 0) {
|
|
774
|
+
// No block at all, not an empty one: `motion: {best: {}}` reads
|
|
775
|
+
// as "the dial ran and found nothing worth showing", which is
|
|
776
|
+
// indistinguishable from a corpus that has nothing at this tier.
|
|
777
|
+
// The gap says which, and the absent key says the rest.
|
|
778
|
+
gaps.push(`no motion row at tier '${tier}' matched the query — the dial narrowed to nothing`);
|
|
779
|
+
} else {
|
|
780
|
+
motion_selection = {
|
|
781
|
+
tier,
|
|
782
|
+
best: rows[0] as Row,
|
|
783
|
+
alternatives: rows.slice(1),
|
|
784
|
+
confidence: motion_result.confidence,
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
644
790
|
// 6 — aggregate confidence (weakest link wins).
|
|
645
791
|
const numeric = domain_confidences.length > 0 ? pyRound(Math.min(...domain_confidences), 3) : 0.0;
|
|
646
792
|
const label = numeric >= 0.4 ? 'high' : numeric >= 0.15 ? 'medium' : 'low';
|
|
@@ -653,7 +799,7 @@ export async function ground(
|
|
|
653
799
|
}
|
|
654
800
|
}
|
|
655
801
|
|
|
656
|
-
|
|
802
|
+
const out: ResultDict = {
|
|
657
803
|
domain: manifest.domain,
|
|
658
804
|
query,
|
|
659
805
|
category,
|
|
@@ -664,6 +810,24 @@ export async function ground(
|
|
|
664
810
|
evidence_gap:
|
|
665
811
|
gaps.length > 0 ? gaps : ['none — every planned domain returned a scored match'],
|
|
666
812
|
};
|
|
813
|
+
|
|
814
|
+
// Dial output is ADDITIVE and omitted entirely when no dial was passed, so
|
|
815
|
+
// the no-flag result stays byte-identical to what it was before dials
|
|
816
|
+
// existed. `spacing_scale` is a top-level override because it replaces a
|
|
817
|
+
// scale rather than informing one.
|
|
818
|
+
if (variance_info || motion_info || density_info) {
|
|
819
|
+
out.dials = {
|
|
820
|
+
variance: variance_info ? variance_info.value : null,
|
|
821
|
+
variance_label: variance_info ? variance_info.label : null,
|
|
822
|
+
motion: motion_info ? motion_info.value : null,
|
|
823
|
+
motion_label: motion_info ? motion_info.label : null,
|
|
824
|
+
density: density_info ? density_info.value : null,
|
|
825
|
+
density_label: density_info ? density_info.label : null,
|
|
826
|
+
};
|
|
827
|
+
if (motion_selection) out.motion = motion_selection;
|
|
828
|
+
if (density_info) out.spacing_scale = density_info.spacing;
|
|
829
|
+
}
|
|
830
|
+
return out;
|
|
667
831
|
}
|
|
668
832
|
|
|
669
833
|
/** Python int(x) for a plan value (int or numeric string). */
|
|
@@ -745,6 +909,50 @@ export function _render_markdown(grounded: ResultDict, master = false): string {
|
|
|
745
909
|
`- **Confidence:** ${pyStr(conf.label ?? '?')} ` + `(${_confScore(conf.score)})`,
|
|
746
910
|
'',
|
|
747
911
|
);
|
|
912
|
+
|
|
913
|
+
// Dials render HERE, not only under --json. This is the whole consumption
|
|
914
|
+
// path for two of the three output paths: the default text render and the
|
|
915
|
+
// persisted MASTER.md both come through this function, and `--density`'s
|
|
916
|
+
// only payload is `spacing_scale` — omitted here, that dial is inert
|
|
917
|
+
// everywhere a human actually reads the result.
|
|
918
|
+
const dials = (grounded.dials as ResultDict | null) || null;
|
|
919
|
+
if (dials) {
|
|
920
|
+
const dialLines: string[] = [];
|
|
921
|
+
for (const name of ['variance', 'motion', 'density']) {
|
|
922
|
+
const value = dials[name];
|
|
923
|
+
if (value === null || value === undefined) {
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
const label = dials[`${name}_label`];
|
|
927
|
+
dialLines.push(
|
|
928
|
+
`- **${name[0]?.toUpperCase()}${name.slice(1)}:** ${pyStr(value)}/10` +
|
|
929
|
+
(label ? ` — ${pyStr(label)}` : ''),
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
if (dialLines.length > 0) {
|
|
933
|
+
lines.push('### Dials', '', ...dialLines, '');
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
const spacing = (grounded.spacing_scale as ResultDict | null) || null;
|
|
938
|
+
if (spacing && Object.keys(spacing).length > 0) {
|
|
939
|
+
lines.push('### Spacing scale (density override)', '');
|
|
940
|
+
for (const step of Object.keys(spacing)) {
|
|
941
|
+
lines.push(`- **${step}:** ${pyStr(spacing[step])}`);
|
|
942
|
+
}
|
|
943
|
+
lines.push('');
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
const motion = (grounded.motion as ResultDict | null) || null;
|
|
947
|
+
const motionBest = motion ? ((motion.best as Row | null) || {}) : {};
|
|
948
|
+
if (motion && Object.keys(motionBest).length > 0) {
|
|
949
|
+
lines.push(`### Motion (tier: ${pyStr(motion.tier ?? '')})`, '');
|
|
950
|
+
for (const key of Object.keys(motionBest)) {
|
|
951
|
+
lines.push(`- **${key}:** ${pyStr(motionBest[key])}`);
|
|
952
|
+
}
|
|
953
|
+
lines.push('');
|
|
954
|
+
}
|
|
955
|
+
|
|
748
956
|
const selections = (grounded.selections as ResultDict | null) || {};
|
|
749
957
|
for (const domain of Object.keys(selections)) {
|
|
750
958
|
const sel = (selections[domain] as ResultDict | null) || {};
|
|
@@ -125,6 +125,28 @@ interface Args {
|
|
|
125
125
|
persist?: string | null;
|
|
126
126
|
project_name?: string | null;
|
|
127
127
|
page?: string | null;
|
|
128
|
+
/** Optional 1-10 design dials; `null` means "not passed", never a default. */
|
|
129
|
+
variance?: number | null;
|
|
130
|
+
motion?: number | null;
|
|
131
|
+
density?: number | null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Parse a 1-10 dial value.
|
|
136
|
+
*
|
|
137
|
+
* A non-numeric value is rejected rather than clamped: `--motion high` is a
|
|
138
|
+
* mistake the caller can fix, and silently reading it as "no dial" would run
|
|
139
|
+
* the whole grounding pass and return a result that quietly ignored the flag.
|
|
140
|
+
* Out-of-RANGE numbers are a different case and are clamped downstream — 11 is
|
|
141
|
+
* a typo with an obvious intent, `high` is not a number at all.
|
|
142
|
+
*/
|
|
143
|
+
function _dial(flag: string, raw: string): number {
|
|
144
|
+
const n = Number(raw);
|
|
145
|
+
if (!Number.isFinite(n)) {
|
|
146
|
+
process.stderr.write(`argument ${flag}: expected an integer 1-10, got '${raw}'\n`);
|
|
147
|
+
process.exit(2);
|
|
148
|
+
}
|
|
149
|
+
return Math.trunc(n);
|
|
128
150
|
}
|
|
129
151
|
|
|
130
152
|
/**
|
|
@@ -212,6 +234,18 @@ function _parseArgs(argv: string[]): Args {
|
|
|
212
234
|
args.page = eat();
|
|
213
235
|
} else if (a.startsWith('--page=')) {
|
|
214
236
|
args.page = a.slice('--page='.length);
|
|
237
|
+
} else if (a === '--variance') {
|
|
238
|
+
args.variance = _dial('--variance', eat());
|
|
239
|
+
} else if (a.startsWith('--variance=')) {
|
|
240
|
+
args.variance = _dial('--variance', a.slice('--variance='.length));
|
|
241
|
+
} else if (a === '--motion') {
|
|
242
|
+
args.motion = _dial('--motion', eat());
|
|
243
|
+
} else if (a.startsWith('--motion=')) {
|
|
244
|
+
args.motion = _dial('--motion', a.slice('--motion='.length));
|
|
245
|
+
} else if (a === '--density') {
|
|
246
|
+
args.density = _dial('--density', eat());
|
|
247
|
+
} else if (a.startsWith('--density=')) {
|
|
248
|
+
args.density = _dial('--density', a.slice('--density='.length));
|
|
215
249
|
} else {
|
|
216
250
|
positionals.push(a);
|
|
217
251
|
}
|
|
@@ -293,7 +327,11 @@ export async function main(argv: string[] | null = null): Promise<number> {
|
|
|
293
327
|
|
|
294
328
|
// ground
|
|
295
329
|
const context = args.context ? (JSON.parse(args.context) as Record<string, unknown>) : {};
|
|
296
|
-
const grounded = await run_ground(manifest, args.query as string, context
|
|
330
|
+
const grounded = await run_ground(manifest, args.query as string, context, {
|
|
331
|
+
variance: args.variance ?? null,
|
|
332
|
+
motion: args.motion ?? null,
|
|
333
|
+
density: args.density ?? null,
|
|
334
|
+
});
|
|
297
335
|
if (args.persist) {
|
|
298
336
|
const info = persist_grounding(
|
|
299
337
|
grounded,
|
|
@@ -8,9 +8,26 @@ upstream repository below. Keep this file shipping with the skill; update the
|
|
|
8
8
|
|
|
9
9
|
- Repository: `https://github.com/nextlevelbuilder/ui-ux-pro-max-skill`
|
|
10
10
|
- Commit SHA: `b7e3af80f6e331f6fb456667b82b12cade7c9d35`
|
|
11
|
-
- Last checked: 2026-
|
|
11
|
+
- Last checked: 2026-08-13
|
|
12
12
|
- Upstream version: `skill.json` v2.5.x (npm `uipro-cli`)
|
|
13
13
|
|
|
14
|
+
### Partial adoptions from a later upstream commit
|
|
15
|
+
|
|
16
|
+
The base pin above still describes the corpus as a whole. Two assets were
|
|
17
|
+
adopted **selectively** from `97eb2a20f...` on 2026-08-13, and the base pin is
|
|
18
|
+
deliberately NOT bumped: at that commit nine files are absent from our `data/`
|
|
19
|
+
and eleven more have drifted, so claiming the newer SHA wholesale would assert
|
|
20
|
+
an adoption that did not happen (six of the absent files are desktop-UI stacks
|
|
21
|
+
outside this pack's scope, and `google-fonts.csv` is declined by ADR-061 § 8).
|
|
22
|
+
|
|
23
|
+
| Asset | From | Why |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| `data/motion.csv` (16 rows) | `97eb2a20` | Consumed by the `gsap` search domain and by the `--motion` design dial, which filters its `Intensity Tier` column. Without it the dial is inert. |
|
|
26
|
+
| The three design dials (`variance` / `motion` / `density`) in `corpus-grounding/scripts/decision_engine.ts` + `ground.ts` | `97eb2a20`, `scripts/design_system.py` | Re-derived, not copied — see `provenance/borrows.jsonl`. |
|
|
27
|
+
|
|
28
|
+
Licence re-verified at `97eb2a20` before adopting: MIT, same copyright holder,
|
|
29
|
+
unchanged from the base pin.
|
|
30
|
+
|
|
14
31
|
## Licenses
|
|
15
32
|
|
|
16
33
|
### MIT — corpus + core engine (root `LICENSE`)
|
|
@@ -18,7 +35,7 @@ upstream repository below. Keep this file shipping with the skill; update the
|
|
|
18
35
|
Copyright (c) 2024 **Next Level Builder**. The tabular corpus
|
|
19
36
|
(`ui-reasoning`, `products`, `colors`, `styles`, `typography`, `charts`,
|
|
20
37
|
`landing`, `icons`, `ux-guidelines`, `react-performance`, `app-interface`,
|
|
21
|
-
`stacks/*`) and the Python engine sources (`core.py`, `search.py`,
|
|
38
|
+
`motion`, `stacks/*`) and the Python engine sources (`core.py`, `search.py`,
|
|
22
39
|
`design_system.py`) are MIT-licensed. Obligation: **retain this notice**.
|
|
23
40
|
|
|
24
41
|
### Apache-2.0 — `ui-styling`-derived material ("claudekit")
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"upstream": {
|
|
3
3
|
"sha": "b7e3af80f6e331f6fb456667b82b12cade7c9d35",
|
|
4
4
|
"repo": "https://github.com/nextlevelbuilder/ui-ux-pro-max-skill",
|
|
5
|
-
"last_checked": "2026-
|
|
5
|
+
"last_checked": "2026-08-13",
|
|
6
6
|
"last_eval": {
|
|
7
7
|
"date": "2026-06-16",
|
|
8
8
|
"model": "claude-sonnet-4-5",
|
|
@@ -257,6 +257,29 @@
|
|
|
257
257
|
"Severity"
|
|
258
258
|
],
|
|
259
259
|
"max_results": 3
|
|
260
|
+
},
|
|
261
|
+
"gsap": {
|
|
262
|
+
"file": "motion.csv",
|
|
263
|
+
"search_cols": [
|
|
264
|
+
"Category",
|
|
265
|
+
"Intensity Tier",
|
|
266
|
+
"Keywords",
|
|
267
|
+
"Trigger"
|
|
268
|
+
],
|
|
269
|
+
"output_cols": [
|
|
270
|
+
"Category",
|
|
271
|
+
"Intensity Tier",
|
|
272
|
+
"Keywords",
|
|
273
|
+
"Trigger",
|
|
274
|
+
"Duration",
|
|
275
|
+
"Easing",
|
|
276
|
+
"GSAP Snippet",
|
|
277
|
+
"Framework Notes",
|
|
278
|
+
"Do",
|
|
279
|
+
"Don't",
|
|
280
|
+
"Performance Notes"
|
|
281
|
+
],
|
|
282
|
+
"max_results": 3
|
|
260
283
|
}
|
|
261
284
|
},
|
|
262
285
|
"detect": {
|
|
@@ -454,6 +477,18 @@
|
|
|
454
477
|
"form",
|
|
455
478
|
"input type",
|
|
456
479
|
"preconnect"
|
|
480
|
+
],
|
|
481
|
+
"gsap": [
|
|
482
|
+
"motion",
|
|
483
|
+
"animation",
|
|
484
|
+
"animate",
|
|
485
|
+
"transition",
|
|
486
|
+
"easing",
|
|
487
|
+
"gsap",
|
|
488
|
+
"hover effect",
|
|
489
|
+
"scroll reveal",
|
|
490
|
+
"parallax",
|
|
491
|
+
"stagger"
|
|
457
492
|
]
|
|
458
493
|
},
|
|
459
494
|
"reasoning": {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
No,Category,Intensity Tier,Keywords,Trigger,Duration,Easing,GSAP Snippet,Framework Notes,Do,Don't,Performance Notes
|
|
2
|
+
1,Hover Micro-interaction,Subtle,"hover, button, opacity, lift, press feedback",hover,150-200ms,power1.out,"gsap.to(el, { y: -1, opacity: 0.9, duration: 0.15, ease: 'power1.out' });",Bind on mouseenter/mouseleave; in React wrap in a ref + useEffect (or onMouseEnter/onMouseLeave props directly calling gsap.to),Keep displacement under 2px so it reads as feedback not motion,Don't animate layout-affecting props (width/height/margin) on hover,Runs on transform/opacity only so it stays on the compositor thread
|
|
3
|
+
2,Hover Micro-interaction,Standard,"hover, card, scale, tilt, cursor feedback",hover,200-300ms,power2.out,"gsap.to(el, { y: -4, scale: 1.02, boxShadow: '0 12px 24px rgba(0,0,0,0.12)', duration: 0.25, ease: 'power2.out' });","Use gsap.quickTo(el, 'y') for cards with many hover targets to avoid re-creating tweens every event",Pair with a matching mouseleave tween that reverses the same properties,Don't leave the hover state stuck if the pointer leaves fast; always attach the reverse tween,quickTo() avoids GC churn on lists with 20+ hoverable cards
|
|
4
|
+
3,Hover Micro-interaction,Complex,"hover, magnetic, cursor follow, 3d tilt",hover + mousemove,300-500ms,"elastic.out(1,0.4)","const xTo = gsap.quickTo(el, 'x', { duration: 0.4, ease: 'elastic.out(1,0.4)' }); const yTo = gsap.quickTo(el, 'y', { duration: 0.4, ease: 'elastic.out(1,0.4)' }); el.addEventListener('mousemove', (e) => { const r = el.getBoundingClientRect(); xTo((e.clientX - r.left - r.width/2) * 0.3); yTo((e.clientY - r.top - r.height/2) * 0.3); });",Debounce is not needed since quickTo interpolates; remove listeners on component unmount in React/Vue to avoid leaks,Clamp the pull strength (e.g. * 0.3) so the element never fully leaves its hit box,Don't apply magnetic effect to more than 1-2 focal elements per screen; it becomes noisy,Use will-change: transform on the target element for smoother compositing
|
|
5
|
+
4,Scroll Reveal,Subtle,"scroll, fade in, reveal, on view",scroll (viewport enter),300-400ms,power1.out,"gsap.from(el, { opacity: 0, y: 12, duration: 0.35, ease: 'power1.out', scrollTrigger: { trigger: el, start: 'top 90%', toggleActions: 'play none none reverse' } });",Requires the ScrollTrigger plugin registered once via gsap.registerPlugin(ScrollTrigger),"Keep the y offset small (8-16px) so it reads as a fade, not a slide",Don't reveal below-the-fold content needed for SEO/crawlers as invisible-by-default without a no-JS fallback,toggleActions 'play none none reverse' avoids re-triggering on every scroll direction change
|
|
6
|
+
5,Scroll Reveal,Standard,"scroll, slide up, staggered section, reveal",scroll (viewport enter),400-600ms,power2.out,"gsap.from(el.children, { opacity: 0, y: 24, duration: 0.5, stagger: 0.08, ease: 'power2.out', scrollTrigger: { trigger: el, start: 'top 85%' } });","In React use useGSAP(() => {...}, { scope: containerRef }) from @gsap/react to auto-cleanup on unmount",Scope the ScrollTrigger to the section container so it doesn't re-scan the whole page,Don't stagger more than ~8 children; beyond that the last items feel laggy,Set scroller/markers: false in production; markers is dev-only
|
|
7
|
+
6,Scroll Reveal,Complex,"scroll, pin, scrub, storytelling, scrollytelling",scroll (continuous scrub),tied to scroll position,none (scrub-driven),"gsap.timeline({ scrollTrigger: { trigger: section, start: 'top top', end: '+=150%', scrub: 1, pin: true } }).from('.headline', { opacity: 0, y: 40 }).to('.bg-layer', { yPercent: -20 }, '<');",Pinning needs the section to have deterministic height; recalc ScrollTrigger.refresh() after images/fonts load,Use scrub: true or a small number (0.5-1.5) instead of instant jumps so it feels tied to the scrollbar,Don't pin more than 1-2 sections per page; excessive pinning fights native scroll feel and hurts mobile UX,"Pinning forces layout reflow; test on mid-tier mobile devices, not just desktop"
|
|
8
|
+
7,Stagger List,Subtle,"list, stagger, cards, grid entrance",load or scroll,250-350ms,power1.out,"gsap.from('.list-item', { opacity: 0, y: 8, duration: 0.3, stagger: 0.03 });",Select items with a stable class/data-attribute (not array index) so re-renders in React don't break targeting,Keep per-item stagger delay small (0.02-0.04s) for lists longer than 10 items,Don't stagger by more than 0.1s per item on long lists; total reveal time becomes sluggish,"For virtualized lists, only animate items currently mounted in the DOM"
|
|
9
|
+
8,Stagger List,Standard,"grid, bento, cards, staggered scale",load or scroll,300-450ms,back.out(1.4),"gsap.from('.grid-item', { opacity: 0, scale: 0.92, y: 16, duration: 0.4, stagger: { each: 0.06, from: 'start', grid: 'auto' }, ease: 'back.out(1.4)' });",grid: 'auto' lets GSAP infer rows/columns from a CSS grid layout for a natural wave stagger,Combine with from: 'center' for a bento-grid layout to draw the eye inward first,Don't use back.out on dense data tables; the overshoot reads as sloppy on informational UI,Group DOM writes; avoid interleaving layout reads (getBoundingClientRect) between staggered tweens
|
|
10
|
+
9,Stagger List,Complex,"stagger, wave, text reveal, split text",load or scroll,400-700ms,expo.out,"const split = new SplitText(headline, { type: 'chars' }); gsap.from(split.chars, { opacity: 0, y: 20, rotateX: -40, duration: 0.6, stagger: 0.015, ease: 'expo.out' });",SplitText is a GSAP Club/paid plugin; confirm license before shipping and provide a plain fade fallback if unavailable,Revert SplitText on unmount/cleanup (split.revert()) to restore original text nodes for accessibility tools,Don't split-animate long paragraphs; reserve for short headlines (under ~8 words),Splitting text creates one element per character; keep it to headline-length copy only for DOM size
|
|
11
|
+
10,Page Transition,Subtle,"route change, fade, page transition",route change,200-300ms,power1.inOut,"gsap.to(main, { opacity: 0, duration: 0.2, onComplete: () => { navigate(); gsap.fromTo(main, { opacity: 0 }, { opacity: 1, duration: 0.2 }); } });","Pair with the router's transition hooks (Next.js App Router transitions, React Router's useNavigate, Vue Router's beforeEach/afterEach)",Preload the destination route's critical assets before the exit tween finishes,Don't block navigation on animation; cap exit duration at ~250ms so the app never feels unresponsive,Exit animation should always resolve faster than entrance (asymmetric timing) so back/forward feels snappy
|
|
12
|
+
11,Page Transition,Standard,"route change, slide, overlay wipe",route change,400-600ms,power2.inOut,"const tl = gsap.timeline(); tl.to('.transition-overlay', { yPercent: 0, duration: 0.4, ease: 'power2.inOut' }).call(navigate).to('.transition-overlay', { yPercent: -100, duration: 0.4, ease: 'power2.inOut', delay: 0.1 });",Keep the overlay element mounted at the layout root (outside the page component) so it survives the route swap,Show a lightweight loading indicator if the destination route's data fetch outlasts the overlay,Don't tie the overlay's reveal directly to data-fetch completion without a max-wait timeout; a slow API stalls the whole transition,Prefer CSS transform (yPercent) over top/left to keep the overlay animation on the compositor thread
|
|
13
|
+
12,Page Transition,Complex,"shared element, morph, hero transition",route change,500-800ms,expo.inOut,"const state = Flip.getState('.hero-image'); navigate(); Flip.from(state, { duration: 0.6, ease: 'expo.inOut', absolute: true, zIndex: 100 });",Requires the GSAP Flip plugin; the 'from' and 'to' route must render the same element with a shared data-flip-id,Verify the shared element exists in both DOM states before calling Flip.from to avoid a silent no-op,Don't use shared-element transitions across more than one element pair per navigation; compounding Flips are hard to time correctly,Flip recalculates layout (FLIP technique) so test on low-end devices for jank
|
|
14
|
+
13,Parallax Scroll,Subtle,"parallax, background, depth, scroll speed",scroll (continuous),tied to scroll position,linear (scrub),"gsap.to('.bg-layer', { yPercent: 10, ease: 'none', scrollTrigger: { trigger: section, scrub: true } });","Apply parallax to background/decorative layers only, never to text or interactive controls",Keep the yPercent delta small (5-15) so foreground and background never desync distractingly,Don't parallax body copy; it hurts reading comfort and can trigger motion sickness,will-change: transform on the parallax layer only; remove it after scroll settles to free GPU memory
|
|
15
|
+
14,Parallax Scroll,Standard,"multi-layer parallax, depth, hero background",scroll (continuous),tied to scroll position,linear (scrub),"gsap.utils.toArray('.parallax-layer').forEach((layer, i) => { gsap.to(layer, { yPercent: (i + 1) * -8, ease: 'none', scrollTrigger: { trigger: layer.parentElement, scrub: 0.5 } }); });",Layer count beyond 3-4 has diminishing visual return and multiplies scroll-listener cost,"Vary speed per layer (background slowest, foreground fastest) to sell the depth illusion",Don't let parallax layers overflow their container; clip with overflow: hidden on the wrapper,Batch all layers under one ScrollTrigger container where possible instead of one per layer
|
|
16
|
+
15,Loading / Skeleton,Subtle,"loading, skeleton, shimmer, pulse",on mount / async wait,1200-1600ms loop,sine.inOut,"gsap.to('.skeleton', { backgroundPosition: '200% 0', duration: 1.4, ease: 'sine.inOut', repeat: -1 });",Kill the loop tween (tween.kill()) as soon as real content mounts to avoid orphaned repeating animations,Use a CSS gradient background-position sweep rather than opacity pulsing; reads as 'loading' more clearly,Don't run more than one shimmer loop per skeleton group; sync them under one timeline so the wave reads as a single unit,repeat: -1 tweens are cheap but must be explicitly killed on unmount or they leak in SPA route changes
|
|
17
|
+
16,Loading / Skeleton,Standard,"progress, spinner, morphing loader",on mount / async wait,800-1200ms loop,power1.inOut,"gsap.timeline({ repeat: -1 }).to('.loader-dot', { y: -8, duration: 0.4, stagger: { each: 0.15, yoyo: true, repeat: 1 } });",Wrap the whole loop timeline in useGSAP with { revertOnUpdate: false } in React so it isn't rebuilt every render,Cap total loop duration under ~1.5s so long waits don't feel like the UI froze on a single beat,Don't use elaborate loaders for sub-300ms waits; they flash and feel worse than no indicator,Pause the timeline (tl.pause()) when the loading tab/view is not visible to save CPU on background tabs
|
|
@@ -52,9 +52,15 @@ gh pr view <number> --json number,state,mergeStateStatus,mergedAt,baseRefName
|
|
|
52
52
|
- **Before merging** → re-fetch and re-read `state` + `mergeStateStatus` in the
|
|
53
53
|
**same turn**; never merge on a status seen earlier in the conversation.
|
|
54
54
|
- **"Based on main" / "current"** → prove it with
|
|
55
|
-
`
|
|
56
|
-
branch is behind and is **not** current — merge
|
|
57
|
-
|
|
55
|
+
`npx tsx node_modules/@event4u/agent-config/src/scripts/check_branch_freshness.ts`;
|
|
56
|
+
exit `1` ⇒ the branch is behind and is **not** current — merge the base in,
|
|
57
|
+
regenerate the derived files, then open the PR (see
|
|
58
|
+
[`/create-pr`](../../commands/pr/create.md) § 1b).
|
|
59
|
+
Prefer it over `git rev-list --count HEAD..origin/main`, which is wrong twice
|
|
60
|
+
over: it pins `main` as the base for a branch whose PR may target something
|
|
61
|
+
else, and it reads the local tracking ref — a fetch from earlier in the
|
|
62
|
+
session, which is memory rather than a check. The gate asks the remote and
|
|
63
|
+
resolves the base from the open PR.
|
|
58
64
|
|
|
59
65
|
## Conventions
|
|
60
66
|
|
|
@@ -302,14 +302,17 @@ prefer the cheaper one (`do-and-judge` < `do-and-judge-two-stage` <
|
|
|
302
302
|
`do-in-steps` < `do-in-parallel` < `do-competitively` <
|
|
303
303
|
`judge-with-debate` < `do-in-worktrees`).
|
|
304
304
|
|
|
305
|
-
**Mode 6 (`do-in-worktrees`) is
|
|
306
|
-
|
|
305
|
+
**Mode 6 (`do-in-worktrees`) is instruction-only** (ADR-229). There is no
|
|
306
|
+
setting to resolve — `worktrees.mode` was deleted:
|
|
307
307
|
|
|
308
|
-
|
|
|
308
|
+
| Situation | Mode 6 |
|
|
309
309
|
|---|---|
|
|
310
|
-
|
|
|
311
|
-
|
|
|
312
|
-
|
|
310
|
+
| The user asked for a worktree chain in the chat ("do this in a worktree", "use mode 6") | Eligible. Proceed via [`using-git-worktrees`](../using-git-worktrees/SKILL.md); the request is the permission, so there is no further ask. |
|
|
311
|
+
| Anything else | **Not eligible**, however well the chain shape fits. Fall back to mode 3 (`do-in-steps`) — the same step-by-step chain, in place on the current branch. Do not offer mode 6 as an option. |
|
|
312
|
+
|
|
313
|
+
Because mode 6 is unreachable unprompted, it is effectively off the
|
|
314
|
+
cheapness ladder above unless the user named it; read that ordering as
|
|
315
|
+
covering modes 1–8 in the default case.
|
|
313
316
|
|
|
314
317
|
### 4. Dispatch
|
|
315
318
|
|
|
@@ -48,30 +48,34 @@ work and makes it impossible to tell what you broke.
|
|
|
48
48
|
|
|
49
49
|
## Procedure
|
|
50
50
|
|
|
51
|
-
### 0. Pre-flight — read
|
|
51
|
+
### 0. Pre-flight — instruction-only, no setting to read
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
```
|
|
54
|
+
NEVER CREATE A WORKTREE THE USER DID NOT ASK FOR.
|
|
55
|
+
THERE IS NO SETTING. THE ONLY TRIGGER IS THE USER SAYING SO IN THE CHAT.
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
There is nothing to read: `worktrees.mode` was deleted (ADR-229). Creation
|
|
59
|
+
is instruction-only and hardcoded, so the decision is never the agent's.
|
|
56
60
|
|
|
57
|
-
|
|
|
61
|
+
| Situation | Behaviour |
|
|
58
62
|
|---|---|
|
|
59
|
-
|
|
|
60
|
-
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
the
|
|
67
|
-
|
|
68
|
-
**
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
63
|
+
| The user asked for a worktree **in the chat** — "do this in a worktree", "use mode 6", "spawn a worktree for X", or the same in another language | Continue to step 1. No permission question: the request **is** the permission. |
|
|
64
|
+
| Anything else, however well the shape would fit | **Do not create one.** Continue in place. |
|
|
65
|
+
|
|
66
|
+
**No explicit request** → do not ask, do not offer, do not mention it. Use
|
|
67
|
+
the in-place path (`subagent-orchestration` mode 3 `do-in-steps`, or just
|
|
68
|
+
stay on the current branch) and say nothing about worktrees. Proposing one
|
|
69
|
+
unprompted is the failure this rule exists to stop — it puts a decision the
|
|
70
|
+
user did not raise in front of them, and the answer is nearly always no.
|
|
71
|
+
|
|
72
|
+
This suppresses **unprompted** usage only. The tool stays fully available
|
|
73
|
+
the moment the user wants it, with no confirmation loop in the way: an
|
|
74
|
+
explicit request goes straight to step 1.
|
|
75
|
+
|
|
76
|
+
The Iron-Law gates below are unchanged and still run on every
|
|
77
|
+
explicitly-requested worktree — ignore-safety check (step 3) and clean
|
|
78
|
+
baseline (step 5). Instruction-only removes the *choice*, never the checks.
|
|
75
79
|
|
|
76
80
|
### 1. Inspect current state
|
|
77
81
|
|
|
@@ -575,7 +575,6 @@ the canonical narrative lives in
|
|
|
575
575
|
| `subagents.implementer_model` | model alias or empty | _(empty)_ | Model for implementer subagents. Empty = same tier as session model. See [subagent-configuration](../contexts/subagent-configuration.md). |
|
|
576
576
|
| `subagents.judge_model` | model alias or empty | _(empty)_ | Model for judge subagents. Empty = one tier above implementer (opus if sonnet, sonnet if haiku). |
|
|
577
577
|
| `subagents.max_parallel` | integer | `3` | Maximum parallel subagent invocations. `1` serializes. |
|
|
578
|
-
| `worktrees.mode` | `off`, `on`, `ask` | `ask` | Controls autonomous `git worktree` usage. `off` = skill refuses unless the user explicitly asks for a worktree that turn (then it runs); `subagent-orchestration` mode 6 falls back to mode 3. `on` = standing permission (skill skips the per-creation ask; ignore-check and clean-baseline gates still apply). `ask` = status quo — `scope-control` permission gate runs every time. |
|
|
579
578
|
| `roles.default_role` | `""`, `developer`, `reviewer`, `tester`, `po`, `incident`, `planner` | _(empty)_ | Role the agent defaults to at the start of a session. Lives in the PROJECT settings (`agent-project-settings.example.yml` § roles), not in the personal template. See [`role-contracts`](../docs/guidelines/agent-infra/role-contracts.md). |
|
|
580
579
|
| `roles.active_role` | same as `default_role` | _(empty)_ | Role currently active; set by `/mode <name>`, cleared by `/mode none`. Enables the `role-mode-adherence` rule. |
|
|
581
580
|
| `personas.override` | list of persona ids | `[]` | Developer-local override of the team default lens cast (not in the shipped personal template — add the block when needed). Empty = inherit `personas.default` from `.agent-project-settings.yml`. See [`layered-settings`](../docs/guidelines/agent-infra/layered-settings.md). |
|
|
@@ -39,7 +39,7 @@ schema_version: 1
|
|
|
39
39
|
# CI guard: a release bump of `package.json` must update this value
|
|
40
40
|
# in lockstep — see scripts/check_template_pin_drift.ts (road-to-
|
|
41
41
|
# portable-runtime-and-update-check P3.3).
|
|
42
|
-
agent_config_version: "
|
|
42
|
+
agent_config_version: "11.0.0"
|
|
43
43
|
|
|
44
44
|
# --- Project identity ---
|
|
45
45
|
project:
|
|
@@ -4055,7 +4055,6 @@ var accessStyle = external_exports.enum(["getters_setters", "get_attribute", "ma
|
|
|
4055
4055
|
var chatFreq = external_exports.enum(["per_turn", "per_phase", "per_tool"]);
|
|
4056
4056
|
var qualityCadence = external_exports.enum(["end_of_roadmap", "per_phase", "per_step"]);
|
|
4057
4057
|
var regenCadence = external_exports.enum(["per_step", "every_5_steps", "phase_boundary"]);
|
|
4058
|
-
var worktreeMode = external_exports.enum(["off", "on", "ask"]);
|
|
4059
4058
|
var fidelityMode = external_exports.enum(["strict", "structural", "hard-floor"]);
|
|
4060
4059
|
var crossSourceMode = external_exports.enum(["on", "auto", "off"]);
|
|
4061
4060
|
var richSkillsMode = external_exports.enum(["on", "ask", "off"]);
|
|
@@ -4375,11 +4374,9 @@ var settingsSchema = external_exports.object({
|
|
|
4375
4374
|
"Opt-in adversarial-verification-council mode (subagent-orchestration Mode 9, ADR-122). off (default) = never runs; ask = offer it on an explicit high-risk change; on = auto-run on high-risk changes. Advisory only \u2014 a panel of distinct-model skeptics red-teams a real change for defect FINDING coverage and NEVER auto-gates it (Hard Floor). Stays default-off until the adversarial-council-finding-coverage claim is backed."
|
|
4376
4375
|
)
|
|
4377
4376
|
}),
|
|
4378
|
-
worktrees
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
)
|
|
4382
|
-
}),
|
|
4377
|
+
// `worktrees.mode` was DELETED (ADR-229). Worktree creation is
|
|
4378
|
+
// instruction-only and hardcoded; there is nothing left to configure. A
|
|
4379
|
+
// leftover key is ignored with one deprecation line via REMOVED_KEYS.
|
|
4383
4380
|
ai_team: external_exports.object({
|
|
4384
4381
|
model: external_exports.string().default("auto").describe(
|
|
4385
4382
|
"Model handed to the codex CLI. 'auto' (default) = pass no --model flag so the CLI's own default applies \u2014 tracks the subscription's current strongest model instead of pinning a stale ID. Any other value passes through verbatim as `--model <value>`."
|
|
@@ -2,10 +2,10 @@ import { createRequire as __acCreateRequire } from 'node:module'; const require
|
|
|
2
2
|
import {
|
|
3
3
|
ZodFirstPartyTypeKind,
|
|
4
4
|
settingsSchema
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-55WZN5VF.js";
|
|
6
6
|
import {
|
|
7
7
|
main
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-PPQO4NGR.js";
|
|
9
9
|
import {
|
|
10
10
|
RULE_SOURCE_REL
|
|
11
11
|
} from "./chunk-BKO2AZ7Z.js";
|
|
@@ -48,7 +48,7 @@ import {
|
|
|
48
48
|
import {
|
|
49
49
|
find_project_root_with_anchor,
|
|
50
50
|
load_agent_settings
|
|
51
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-NLP54U3J.js";
|
|
52
52
|
import {
|
|
53
53
|
event4u_root,
|
|
54
54
|
legacy_xdg_root,
|