@agent-profile/cli 0.1.5 → 0.2.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/dist/index.js CHANGED
@@ -20,12 +20,15 @@ function getDefaultTargetIds() {
20
20
  "lockfile",
21
21
  "tabnine-guidelines",
22
22
  "tabnine-mcp-config",
23
+ "tabnine-subagents",
23
24
  "codex-config",
24
25
  "codex-workflow-skills",
26
+ "codex-subagents",
25
27
  "claude-settings",
26
28
  "claude-mcp",
27
29
  "claude-md",
28
- "claude-workflow-skills"
30
+ "claude-workflow-skills",
31
+ "claude-subagents"
29
32
  ];
30
33
  }
31
34
  function createGeneratedTextFile(path6, target, templateId, text) {
@@ -466,6 +469,33 @@ var ai_profile_schema_default = {
466
469
  },
467
470
  finalReview: {
468
471
  type: "boolean"
472
+ },
473
+ codeReview: {
474
+ type: "boolean"
475
+ },
476
+ refactoring: {
477
+ type: "boolean"
478
+ },
479
+ documentation: {
480
+ type: "boolean"
481
+ },
482
+ subagentDrivenDevelopment: {
483
+ type: "boolean"
484
+ }
485
+ }
486
+ },
487
+ capabilities: {
488
+ type: "object",
489
+ additionalProperties: false,
490
+ properties: {
491
+ delegation: {
492
+ type: "object",
493
+ additionalProperties: false,
494
+ properties: {
495
+ subagents: {
496
+ $ref: "#/definitions/subagents"
497
+ }
498
+ }
469
499
  }
470
500
  }
471
501
  },
@@ -573,11 +603,342 @@ var ai_profile_schema_default = {
573
603
  items: {
574
604
  $ref: "#/definitions/slug"
575
605
  }
606
+ },
607
+ subagents: {
608
+ type: "object",
609
+ additionalProperties: false,
610
+ required: ["enabled"],
611
+ properties: {
612
+ enabled: {
613
+ type: "boolean"
614
+ },
615
+ defaults: {
616
+ type: "object",
617
+ additionalProperties: false,
618
+ properties: {
619
+ maxConcurrent: {
620
+ type: "integer",
621
+ minimum: 1
622
+ },
623
+ maxDepth: {
624
+ type: "integer",
625
+ minimum: 1
626
+ }
627
+ }
628
+ },
629
+ agents: {
630
+ type: "array",
631
+ items: {
632
+ $ref: "#/definitions/subagent"
633
+ }
634
+ }
635
+ },
636
+ if: {
637
+ properties: {
638
+ enabled: {
639
+ const: true
640
+ }
641
+ },
642
+ required: ["enabled"]
643
+ },
644
+ then: {
645
+ required: ["agents"],
646
+ properties: {
647
+ agents: {
648
+ type: "array",
649
+ minItems: 1,
650
+ items: {
651
+ $ref: "#/definitions/subagent"
652
+ }
653
+ }
654
+ }
655
+ }
656
+ },
657
+ subagent: {
658
+ oneOf: [
659
+ { $ref: "#/definitions/subagentTemplateRef" },
660
+ { $ref: "#/definitions/subagentInline" }
661
+ ]
662
+ },
663
+ subagentTemplateRef: {
664
+ type: "object",
665
+ additionalProperties: false,
666
+ required: ["useTemplate"],
667
+ properties: {
668
+ useTemplate: {
669
+ type: "string",
670
+ enum: ["implementer", "spec-reviewer", "code-quality-reviewer"]
671
+ }
672
+ }
673
+ },
674
+ subagentInline: {
675
+ type: "object",
676
+ additionalProperties: false,
677
+ required: ["name", "description", "purpose", "prompt", "toolScope"],
678
+ properties: {
679
+ name: {
680
+ type: "string",
681
+ minLength: 1,
682
+ pattern: "^[a-z0-9][a-z0-9-]*$",
683
+ not: {
684
+ enum: [
685
+ "default",
686
+ "worker",
687
+ "explorer",
688
+ "explore",
689
+ "plan",
690
+ "general-purpose",
691
+ "codebase_investigator",
692
+ "remote-codebase-investigator",
693
+ "generalist",
694
+ "browser_agent"
695
+ ]
696
+ }
697
+ },
698
+ description: {
699
+ type: "string",
700
+ minLength: 1,
701
+ pattern: "^[^\\r\\n]+$"
702
+ },
703
+ purpose: {
704
+ type: "string",
705
+ minLength: 1,
706
+ pattern: "^[^\\r\\n]+$"
707
+ },
708
+ prompt: {
709
+ type: "string",
710
+ minLength: 1
711
+ },
712
+ toolScope: {
713
+ type: "string",
714
+ enum: ["read-only", "workspace-write"]
715
+ },
716
+ modelPreference: {
717
+ type: "string",
718
+ enum: ["inherit", "fast", "balanced", "capable"]
719
+ },
720
+ maxTurns: {
721
+ type: "integer",
722
+ minimum: 1
723
+ },
724
+ timeoutMinutes: {
725
+ type: "integer",
726
+ minimum: 1
727
+ },
728
+ mcpServers: {
729
+ type: "array",
730
+ items: {
731
+ type: "string"
732
+ },
733
+ maxItems: 0
734
+ }
735
+ }
576
736
  }
577
737
  }
578
738
  };
579
739
 
580
740
  // ../../packages/core/src/profile.ts
741
+ var SUBAGENT_TEMPLATE_NAMES = [
742
+ "implementer",
743
+ "spec-reviewer",
744
+ "code-quality-reviewer"
745
+ ];
746
+ function isSubagentTemplateRef(entry) {
747
+ return typeof entry.useTemplate === "string";
748
+ }
749
+ function freezeTemplate(template) {
750
+ if (template.mcpServers !== void 0) {
751
+ Object.freeze(template.mcpServers);
752
+ }
753
+ return Object.freeze(template);
754
+ }
755
+ function cloneTemplate(template) {
756
+ const clone = {
757
+ name: template.name,
758
+ description: template.description,
759
+ purpose: template.purpose,
760
+ prompt: template.prompt,
761
+ toolScope: template.toolScope
762
+ };
763
+ if (template.modelPreference !== void 0)
764
+ clone.modelPreference = template.modelPreference;
765
+ if (template.maxTurns !== void 0) clone.maxTurns = template.maxTurns;
766
+ if (template.timeoutMinutes !== void 0)
767
+ clone.timeoutMinutes = template.timeoutMinutes;
768
+ if (template.mcpServers !== void 0)
769
+ clone.mcpServers = [...template.mcpServers];
770
+ return clone;
771
+ }
772
+ var SUBAGENT_TEMPLATES_RAW = {
773
+ implementer: {
774
+ name: "implementer",
775
+ description: "Use for a bounded implementation task after the parent agent has provided the full task text, relevant spec excerpts, file ownership, constraints, and expected tests. Returns an explicit status and does not commit or push unless the parent request includes that requirement.",
776
+ purpose: "Implement one scoped task with tests, self-review, and honest escalation when requirements or architecture are unclear.",
777
+ prompt: `You are implementing one bounded task.
778
+
779
+ Work only from the task text, spec excerpts, file ownership, constraints,
780
+ and allowed commands provided in the prompt. Do not assume hidden chat
781
+ history. If essential context is missing, report NEEDS_CONTEXT instead of
782
+ guessing.
783
+
784
+ Before editing, restate the goal, non-goals, acceptance criteria, and files
785
+ you expect to touch. If the task is ambiguous, architectural, or broader than
786
+ the prompt says, stop and report BLOCKED or NEEDS_CONTEXT.
787
+
788
+ Implement exactly what the task specifies. Follow the repository's SDD/TDD
789
+ workflow. Add or update focused tests where practical, verify RED before
790
+ behavior changes when the task changes behavior, implement the smallest
791
+ passing change, then verify GREEN. Preserve existing patterns and avoid
792
+ unrelated refactors.
793
+
794
+ Do not commit, push, create branches, install dependencies, access secrets,
795
+ contact production systems, or upload source unless the parent prompt
796
+ explicitly authorizes that action.
797
+
798
+ Before reporting back, self-review for completeness, quality, scope control,
799
+ and test validity. Fix issues you find if they are inside the assigned scope.
800
+
801
+ Report exactly:
802
+ - Status: DONE | DONE_WITH_CONCERNS | BLOCKED | NEEDS_CONTEXT
803
+ - What changed
804
+ - Tests run and results
805
+ - Files changed
806
+ - Self-review findings
807
+ - Concerns, missing context, or follow-up work`,
808
+ toolScope: "workspace-write",
809
+ modelPreference: "balanced",
810
+ maxTurns: 18,
811
+ timeoutMinutes: 20,
812
+ mcpServers: []
813
+ },
814
+ "spec-reviewer": {
815
+ name: "spec-reviewer",
816
+ description: "Use after an implementation worker reports DONE or DONE_WITH_CONCERNS to verify the actual changed files against the task text, approved spec, acceptance criteria, and claimed result. Reads code and docs only; does not edit.",
817
+ purpose: "Catch missing requirements, extra scope, and misunderstandings before code-quality review.",
818
+ prompt: `You are reviewing whether an implementation matches its specification.
819
+
820
+ Work only from the full task text, approved spec excerpts, acceptance
821
+ criteria, changed-file list, and implementer report provided in the prompt.
822
+ Do not assume hidden chat history and do not trust the implementer report
823
+ without checking the actual files.
824
+
825
+ Read the changed code and documentation. Compare actual behavior against the
826
+ requested behavior line by line. Look for missing requirements, extra
827
+ unrequested work, changed contracts, wrong interpretation, and fixture or
828
+ documentation drift.
829
+
830
+ Do not edit files, run broad commands, install dependencies, read secrets,
831
+ contact production systems, or upload source. If the prompt lacks enough
832
+ context to review, report NEEDS_CONTEXT.
833
+
834
+ Report exactly:
835
+ - Status: COMPLIANT | ISSUES_FOUND | NEEDS_CONTEXT
836
+ - Requirements checked
837
+ - Findings with severity, path, and line or symbol when available
838
+ - Extra or out-of-scope work, if any
839
+ - Missing tests or docs tied to acceptance criteria
840
+ - Recommendation: proceed to code-quality review, fix first, or request context`,
841
+ toolScope: "read-only",
842
+ modelPreference: "capable",
843
+ maxTurns: 10,
844
+ timeoutMinutes: 8,
845
+ mcpServers: []
846
+ },
847
+ "code-quality-reviewer": {
848
+ name: "code-quality-reviewer",
849
+ description: "Use only after spec review passes to assess maintainability, decomposition, tests, naming, risky mocks, and local code quality in the changed files. Reads code and docs only; does not edit.",
850
+ purpose: "Catch maintainability and test-quality risks after the implementation is known to match the spec.",
851
+ prompt: `You are reviewing code quality after spec compliance has passed.
852
+
853
+ Work only from the full task text, approved spec excerpts, changed-file list,
854
+ spec-review result, test results, and implementer report provided in the
855
+ prompt. Do not assume hidden chat history.
856
+
857
+ Review only the change's contribution. Do not flag pre-existing file size or
858
+ architecture unless the change makes it materially worse. Check whether each
859
+ touched file has a clear responsibility, whether names describe intent,
860
+ whether complex predicates should be named, whether tests verify behavior
861
+ rather than mocks, whether mocks preserve required real side effects, and
862
+ whether new APIs exist only for tests.
863
+
864
+ Do not edit files, run broad commands, install dependencies, read secrets,
865
+ contact production systems, or upload source. If the prompt lacks enough
866
+ context to review, report NEEDS_CONTEXT.
867
+
868
+ Report exactly:
869
+ - Status: ACCEPTABLE | ISSUES_FOUND | NEEDS_CONTEXT
870
+ - Strengths
871
+ - Issues grouped as Critical, Important, or Minor
872
+ - Test-quality concerns
873
+ - Maintainability concerns
874
+ - Assessment: ready, fix first, or request context`,
875
+ toolScope: "read-only",
876
+ modelPreference: "capable",
877
+ maxTurns: 10,
878
+ timeoutMinutes: 8,
879
+ mcpServers: []
880
+ }
881
+ };
882
+ var SUBAGENT_TEMPLATES = {
883
+ implementer: freezeTemplate(SUBAGENT_TEMPLATES_RAW.implementer),
884
+ "spec-reviewer": freezeTemplate(SUBAGENT_TEMPLATES_RAW["spec-reviewer"]),
885
+ "code-quality-reviewer": freezeTemplate(
886
+ SUBAGENT_TEMPLATES_RAW["code-quality-reviewer"]
887
+ )
888
+ };
889
+ function expandSubagentEntry(entry) {
890
+ if (isSubagentTemplateRef(entry)) {
891
+ return cloneTemplate(SUBAGENT_TEMPLATES[entry.useTemplate]);
892
+ }
893
+ return entry;
894
+ }
895
+ function getSubagentTemplateRefs(profile) {
896
+ const block = profile.capabilities?.delegation?.subagents;
897
+ if (!block || block.enabled !== true) {
898
+ return [];
899
+ }
900
+ const refs = [];
901
+ for (const entry of block.agents ?? []) {
902
+ if (isSubagentTemplateRef(entry)) {
903
+ refs.push(entry.useTemplate);
904
+ }
905
+ }
906
+ return refs;
907
+ }
908
+ var DEFAULT_SUBAGENT_MAX_CONCURRENT = 3;
909
+ var DEFAULT_SUBAGENT_MAX_DEPTH = 1;
910
+ function normalizeSubagentName(name) {
911
+ return name.toLowerCase().replace(/_/gu, "-");
912
+ }
913
+ function getSubagentDefaults(profile) {
914
+ const defaults = profile.capabilities?.delegation?.subagents?.defaults;
915
+ return {
916
+ maxConcurrent: defaults?.maxConcurrent ?? DEFAULT_SUBAGENT_MAX_CONCURRENT,
917
+ maxDepth: defaults?.maxDepth ?? DEFAULT_SUBAGENT_MAX_DEPTH
918
+ };
919
+ }
920
+ function getEnabledSubagents(profile) {
921
+ const block = profile.capabilities?.delegation?.subagents;
922
+ if (!block || block.enabled !== true) {
923
+ return [];
924
+ }
925
+ return (block.agents ?? []).map((entry) => expandSubagentEntry(entry));
926
+ }
927
+ var SUBAGENT_BUILTIN_NAMES_NORMALIZED = /* @__PURE__ */ new Set([
928
+ "default",
929
+ "worker",
930
+ "explorer",
931
+ "explore",
932
+ "plan",
933
+ "general-purpose",
934
+ "codebase-investigator",
935
+ "remote-codebase-investigator",
936
+ "generalist",
937
+ "browser-agent"
938
+ ]);
939
+ function isSubagentBuiltinNameCollision(name) {
940
+ return SUBAGENT_BUILTIN_NAMES_NORMALIZED.has(normalizeSubagentName(name));
941
+ }
581
942
  var compiledValidator;
582
943
  var DEFAULT_SAFETY = {
583
944
  mode: "guarded",
@@ -653,6 +1014,13 @@ function validateProfileValue(value) {
653
1014
  const validate = getValidator();
654
1015
  if (validate(value)) {
655
1016
  const profile = value;
1017
+ const subagentIssues = validateSubagentSemantics(profile);
1018
+ if (subagentIssues.length > 0) {
1019
+ return {
1020
+ ok: false,
1021
+ issues: subagentIssues.sort((left, right) => compareIssues(left, right))
1022
+ };
1023
+ }
656
1024
  return {
657
1025
  ok: true,
658
1026
  profile,
@@ -665,6 +1033,49 @@ function validateProfileValue(value) {
665
1033
  issues: toValidationIssues(validate.errors ?? [], value)
666
1034
  };
667
1035
  }
1036
+ function validateSubagentSemantics(profile) {
1037
+ const subagents = profile.capabilities?.delegation?.subagents;
1038
+ if (!subagents || subagents.enabled !== true) {
1039
+ return [];
1040
+ }
1041
+ const entries = subagents.agents ?? [];
1042
+ const issues = [];
1043
+ const seenRaw = /* @__PURE__ */ new Map();
1044
+ const seenNormalized = /* @__PURE__ */ new Map();
1045
+ entries.forEach((entry, index) => {
1046
+ const expanded = expandSubagentEntry(entry);
1047
+ const name = expanded.name;
1048
+ const rawIndex = seenRaw.get(name);
1049
+ if (rawIndex !== void 0) {
1050
+ issues.push({
1051
+ code: "schema_validation_error",
1052
+ path: `/capabilities/delegation/subagents/agents/${index}/name`,
1053
+ expected: "unique subagent name",
1054
+ actual: "duplicate",
1055
+ message: `/capabilities/delegation/subagents/agents/${index}/name duplicates the name used at index ${rawIndex}.`
1056
+ });
1057
+ } else {
1058
+ seenRaw.set(name, index);
1059
+ }
1060
+ const normalized = normalizeSubagentName(name);
1061
+ const normalizedIndex = seenNormalized.get(normalized);
1062
+ if (normalizedIndex !== void 0 && normalizedIndex !== index) {
1063
+ const isPureDuplicate = rawIndex !== void 0;
1064
+ if (!isPureDuplicate) {
1065
+ issues.push({
1066
+ code: "schema_validation_error",
1067
+ path: `/capabilities/delegation/subagents/agents/${index}/name`,
1068
+ expected: "unique normalized subagent id",
1069
+ actual: "duplicate after hyphen/underscore folding",
1070
+ message: `/capabilities/delegation/subagents/agents/${index}/name collides with the name used at index ${normalizedIndex} after normalization.`
1071
+ });
1072
+ }
1073
+ } else if (normalizedIndex === void 0) {
1074
+ seenNormalized.set(normalized, index);
1075
+ }
1076
+ });
1077
+ return issues;
1078
+ }
668
1079
  function normalizeSafety(profile) {
669
1080
  return {
670
1081
  mode: profile.safety?.mode ?? DEFAULT_SAFETY.mode,
@@ -1432,11 +1843,239 @@ function tokenError(code, message) {
1432
1843
  return { ok: false, code, message };
1433
1844
  }
1434
1845
 
1846
+ // ../../packages/compiler/src/guidance-content.ts
1847
+ var REACT_STACK_TOPIC = {
1848
+ tabnineTitle: "TypeScript and React Stack",
1849
+ agentsMdTitle: "Stack Guidance \u2014 React",
1850
+ intro: "Use the existing TypeScript and React conventions in the repository.",
1851
+ subsections: [
1852
+ {
1853
+ heading: "TypeScript Discipline",
1854
+ bullets: [
1855
+ "Do not use `any` without a documented reason.",
1856
+ "Declare explicit types for exported functions, props, and return values.",
1857
+ "Reuse existing types and utilities before adding new ones."
1858
+ ]
1859
+ },
1860
+ {
1861
+ heading: "Component Conventions",
1862
+ bullets: [
1863
+ "Use function components with typed props.",
1864
+ "Co-locate components with the modules that own them.",
1865
+ "Keep render-only components free of side effects."
1866
+ ]
1867
+ },
1868
+ {
1869
+ heading: "Hook Discipline",
1870
+ bullets: [
1871
+ "Add memoization (`useMemo`, `useCallback`) only when a measured re-render or referential-identity problem exists.",
1872
+ "Keep state local; do not introduce a global store by default.",
1873
+ "Honor the rules of hooks; never call hooks conditionally."
1874
+ ]
1875
+ },
1876
+ {
1877
+ heading: "Styling",
1878
+ bullets: [
1879
+ "Follow the existing styling approach in the repository.",
1880
+ "Do not introduce a new CSS framework or component library."
1881
+ ]
1882
+ },
1883
+ {
1884
+ heading: "API Calls",
1885
+ bullets: [
1886
+ "Reuse existing client utilities for HTTP and data access.",
1887
+ "Type both request payloads and response bodies.",
1888
+ "Handle error and loading states explicitly."
1889
+ ]
1890
+ },
1891
+ {
1892
+ heading: "SDD and TDD Focus",
1893
+ bullets: [
1894
+ "Cover state transitions, API success, error, and loading paths.",
1895
+ "Cover accessibility-affecting behavior (focus, keyboard, ARIA).",
1896
+ "Add or update focused tests before changing observable behavior."
1897
+ ]
1898
+ }
1899
+ ],
1900
+ tabnineChecklistReference: "See `90-final-review.md` for the shared final-review checklist.",
1901
+ agentsMdChecklistReference: "See the `## Completion Checklist` section for shared review steps."
1902
+ };
1903
+ var CODE_REVIEW_TOPIC = {
1904
+ tabnineTitle: "Code Review",
1905
+ agentsMdTitle: "Code Review",
1906
+ intro: "Use these rules when reviewing a pull request or proposed change.",
1907
+ subsections: [
1908
+ {
1909
+ heading: "Review Focus",
1910
+ bullets: [
1911
+ "Correctness and edge cases.",
1912
+ "Security, including input validation and secret handling.",
1913
+ "Performance hotspots and obvious complexity regressions.",
1914
+ "Unnecessary complexity or premature abstraction.",
1915
+ "Consistency with existing project style.",
1916
+ "Missing or weakened tests.",
1917
+ "Weak typing or unjustified `any`.",
1918
+ "Accessibility-affecting behavior in user-facing changes.",
1919
+ "Error handling and observability gaps.",
1920
+ "Dependency risk and license concerns.",
1921
+ "Spec compliance."
1922
+ ]
1923
+ },
1924
+ {
1925
+ heading: "Severity Labels",
1926
+ bullets: [
1927
+ "Blocker: must fix before merge (correctness, security, contract break).",
1928
+ "High: must fix before merge unless explicitly deferred.",
1929
+ "Medium: should fix before merge or open a tracked follow-up.",
1930
+ "Low: nit or polish; safe to defer."
1931
+ ]
1932
+ },
1933
+ {
1934
+ heading: "Output Format",
1935
+ bullets: [
1936
+ "Summary: one paragraph of intent and overall verdict.",
1937
+ "Spec Compliance: cite the spec and any deviation.",
1938
+ "Findings: grouped by severity, each with file, function, and concrete suggestion.",
1939
+ "Tests: list coverage gaps and whether tests were run.",
1940
+ "Safety Review: secrets, production access, dependency installs, network access.",
1941
+ "Final recommendation: approve, request changes, or block with reason."
1942
+ ]
1943
+ },
1944
+ {
1945
+ heading: "Review Discipline",
1946
+ bullets: [
1947
+ "Skip nitpicks the autoformatter or linter already handles.",
1948
+ "Do not propose broad rewrites; keep suggestions actionable.",
1949
+ "Reference the specific file, function, or component in every finding."
1950
+ ]
1951
+ }
1952
+ ],
1953
+ tabnineChecklistReference: "See `90-final-review.md` for the shared final-review checklist.",
1954
+ agentsMdChecklistReference: "See the `## Completion Checklist` section for shared review steps."
1955
+ };
1956
+ var REFACTORING_TOPIC = {
1957
+ tabnineTitle: "Refactoring",
1958
+ agentsMdTitle: "Refactoring",
1959
+ intro: "Use these rules when restructuring code without changing observable behavior.",
1960
+ subsections: [
1961
+ {
1962
+ heading: "Principles",
1963
+ bullets: [
1964
+ "Refactor to remove duplication, clarify intent, or unlock a planned change.",
1965
+ "Do not refactor for style preference alone or to chase a new pattern.",
1966
+ "Keep refactoring commits separate from behavior changes."
1967
+ ]
1968
+ },
1969
+ {
1970
+ heading: "Safe Refactoring Workflow",
1971
+ bullets: [
1972
+ "Identify the code smell or constraint that motivates the change.",
1973
+ "Check existing abstractions before introducing a new one.",
1974
+ "Define expected behavior in tests before restructuring.",
1975
+ "Make the smallest extraction that solves the problem.",
1976
+ "Preserve public behavior; run tests and golden fixtures after each step.",
1977
+ "Summarize what was intentionally not changed in the review notes."
1978
+ ]
1979
+ },
1980
+ {
1981
+ heading: "Restrictions",
1982
+ bullets: [
1983
+ "Do not rename public APIs without explicit approval.",
1984
+ "Do not move files across modules without explicit approval.",
1985
+ "Do not change schemas, endpoint contracts, or build tooling without explicit approval.",
1986
+ "Do not refactor and add features in the same change."
1987
+ ]
1988
+ }
1989
+ ],
1990
+ tabnineChecklistReference: "See `90-final-review.md` for the shared final-review checklist.",
1991
+ agentsMdChecklistReference: "See the `## Completion Checklist` section for shared review steps."
1992
+ };
1993
+ var DOCUMENTATION_TOPIC = {
1994
+ tabnineTitle: "Documentation",
1995
+ agentsMdTitle: "Documentation",
1996
+ intro: "Use these rules when adding or updating project documentation.",
1997
+ subsections: [
1998
+ {
1999
+ heading: "When to Update Documentation",
2000
+ bullets: [
2001
+ "Setup or onboarding steps changed.",
2002
+ "Workflow, command, or build step changed.",
2003
+ "Public API surface changed.",
2004
+ "Configuration or environment variables changed.",
2005
+ "Testing command changed.",
2006
+ "Deployment or release procedure changed.",
2007
+ "Troubleshooting guidance is newly known."
2008
+ ]
2009
+ },
2010
+ {
2011
+ heading: "Style",
2012
+ bullets: [
2013
+ "Write for maintainers, not marketing.",
2014
+ "Provide copy-pasteable commands where applicable.",
2015
+ "Keep examples current; remove examples that no longer run.",
2016
+ "Reference file paths when they help a reader navigate."
2017
+ ]
2018
+ },
2019
+ {
2020
+ heading: "README Rules",
2021
+ bullets: [
2022
+ "Keep the existing structure intact.",
2023
+ "Add only relevant new sections.",
2024
+ "Do not rewrite the README without an explicit request."
2025
+ ]
2026
+ },
2027
+ {
2028
+ heading: "Code Comment Policy",
2029
+ bullets: [
2030
+ "Comment non-obvious business rules and invariants.",
2031
+ "Comment tricky edge cases the code alone does not reveal.",
2032
+ "Comment surprising technical constraints and security-sensitive behavior.",
2033
+ "Do not write comments that restate the code."
2034
+ ]
2035
+ }
2036
+ ],
2037
+ tabnineChecklistReference: "See `90-final-review.md` for the shared final-review checklist.",
2038
+ agentsMdChecklistReference: "See the `## Completion Checklist` section for shared review steps."
2039
+ };
2040
+ function renderTopicAsTabnineGuideline(topic) {
2041
+ const sections = topic.subsections.map(
2042
+ (subsection) => `## ${subsection.heading}
2043
+
2044
+ ${subsection.bullets.map((bullet) => `- ${bullet}`).join("\n")}
2045
+ `
2046
+ ).join("\n");
2047
+ return `<!-- Generated by Agent Profile Compiler. Do not edit by hand. -->
2048
+
2049
+ # ${topic.tabnineTitle}
2050
+
2051
+ ${topic.intro}
2052
+
2053
+ ${sections}
2054
+ ${topic.tabnineChecklistReference}
2055
+ `;
2056
+ }
2057
+ function renderTopicAsAgentsMdSection(topic) {
2058
+ const subsections = topic.subsections.map(
2059
+ (subsection) => `**${subsection.heading}**
2060
+
2061
+ ${subsection.bullets.map((bullet) => `- ${bullet}`).join("\n")}`
2062
+ ).join("\n\n");
2063
+ return `## ${topic.agentsMdTitle}
2064
+
2065
+ ${topic.intro}
2066
+
2067
+ ${subsections}
2068
+
2069
+ ${topic.agentsMdChecklistReference}
2070
+ `;
2071
+ }
2072
+
1435
2073
  // ../../packages/compiler/src/compiler.ts
1436
2074
  var WORKFLOW_SKILLS = [
1437
2075
  { id: "sdd-change", workflowFlag: "sdd" },
1438
2076
  { id: "tdd-change", workflowFlag: "tdd" },
1439
- { id: "final-review", workflowFlag: "finalReview" }
2077
+ { id: "final-review", workflowFlag: "finalReview" },
2078
+ { id: "subagent-driven-change", workflowFlag: "subagentDrivenDevelopment" }
1440
2079
  ];
1441
2080
  var TEMPLATE_SOURCES = [
1442
2081
  {
@@ -1445,6 +2084,22 @@ var TEMPLATE_SOURCES = [
1445
2084
  version: "1",
1446
2085
  source: renderAgentsMdTemplateSource()
1447
2086
  },
2087
+ agentsMdTopicTemplateSource(
2088
+ "targets/agents-md/30-stack-typescript-react@1",
2089
+ REACT_STACK_TOPIC
2090
+ ),
2091
+ agentsMdTopicTemplateSource(
2092
+ "targets/agents-md/60-code-review@1",
2093
+ CODE_REVIEW_TOPIC
2094
+ ),
2095
+ agentsMdTopicTemplateSource(
2096
+ "targets/agents-md/70-refactoring@1",
2097
+ REFACTORING_TOPIC
2098
+ ),
2099
+ agentsMdTopicTemplateSource(
2100
+ "targets/agents-md/80-documentation@1",
2101
+ DOCUMENTATION_TOPIC
2102
+ ),
1448
2103
  {
1449
2104
  id: "targets/lockfile@1",
1450
2105
  target: "lockfile",
@@ -1467,6 +2122,10 @@ var TEMPLATE_SOURCES = [
1467
2122
  "targets/tabnine-guidelines/30-stack-typescript-svelte@1",
1468
2123
  renderTypeScriptSvelteGuideline
1469
2124
  ),
2125
+ guidelineTemplateSource(
2126
+ "targets/tabnine-guidelines/30-stack-typescript-react@1",
2127
+ renderTypeScriptReactGuideline
2128
+ ),
1470
2129
  guidelineTemplateSource(
1471
2130
  "targets/tabnine-guidelines/40-stack-java-spring@1",
1472
2131
  renderJavaSpringGuideline
@@ -1475,6 +2134,18 @@ var TEMPLATE_SOURCES = [
1475
2134
  "targets/tabnine-guidelines/50-testing-playwright-junit@1",
1476
2135
  renderPlaywrightJunitGuideline
1477
2136
  ),
2137
+ guidelineTemplateSource(
2138
+ "targets/tabnine-guidelines/60-code-review@1",
2139
+ renderCodeReviewGuideline
2140
+ ),
2141
+ guidelineTemplateSource(
2142
+ "targets/tabnine-guidelines/70-refactoring@1",
2143
+ renderRefactoringGuideline
2144
+ ),
2145
+ guidelineTemplateSource(
2146
+ "targets/tabnine-guidelines/80-documentation@1",
2147
+ renderDocumentationGuideline
2148
+ ),
1478
2149
  guidelineTemplateSource(
1479
2150
  "targets/tabnine-guidelines/90-final-review@1",
1480
2151
  renderFinalReviewGuideline
@@ -1497,7 +2168,7 @@ var TEMPLATE_SOURCES = [
1497
2168
  "sdd-change"
1498
2169
  ),
1499
2170
  workflowSkillTemplateSource(
1500
- "targets/codex-workflow-skills/tdd-change@1",
2171
+ "targets/codex-workflow-skills/tdd-change@2",
1501
2172
  "codex-workflow-skills",
1502
2173
  "tdd-change"
1503
2174
  ),
@@ -1506,6 +2177,11 @@ var TEMPLATE_SOURCES = [
1506
2177
  "codex-workflow-skills",
1507
2178
  "final-review"
1508
2179
  ),
2180
+ workflowSkillTemplateSource(
2181
+ "targets/codex-workflow-skills/subagent-driven-change@1",
2182
+ "codex-workflow-skills",
2183
+ "subagent-driven-change"
2184
+ ),
1509
2185
  {
1510
2186
  id: "targets/claude-settings@1",
1511
2187
  target: "claude-settings",
@@ -1530,7 +2206,7 @@ var TEMPLATE_SOURCES = [
1530
2206
  "sdd-change"
1531
2207
  ),
1532
2208
  workflowSkillTemplateSource(
1533
- "targets/claude-workflow-skills/tdd-change@1",
2209
+ "targets/claude-workflow-skills/tdd-change@2",
1534
2210
  "claude-workflow-skills",
1535
2211
  "tdd-change"
1536
2212
  ),
@@ -1538,6 +2214,11 @@ var TEMPLATE_SOURCES = [
1538
2214
  "targets/claude-workflow-skills/final-review@1",
1539
2215
  "claude-workflow-skills",
1540
2216
  "final-review"
2217
+ ),
2218
+ workflowSkillTemplateSource(
2219
+ "targets/claude-workflow-skills/subagent-driven-change@1",
2220
+ "claude-workflow-skills",
2221
+ "subagent-driven-change"
1541
2222
  )
1542
2223
  ];
1543
2224
  function getDefaultTemplates() {
@@ -1552,7 +2233,7 @@ function compileProfile(request) {
1552
2233
  const targets = uniqueTargets(
1553
2234
  request.targets ?? getEnabledTargetIds(request.profile)
1554
2235
  );
1555
- const templates = request.templates ?? getDefaultTemplates();
2236
+ const templates = request.templates ?? mergeTemplates(getDefaultTemplates(), getSubagentTemplates(request.profile));
1556
2237
  const issues = validateTargets(request.profile, targets, templates);
1557
2238
  if (issues.length > 0) {
1558
2239
  return {
@@ -1563,15 +2244,81 @@ function compileProfile(request) {
1563
2244
  const files = targets.flatMap(
1564
2245
  (target) => renderTarget(target, request.profile)
1565
2246
  );
2247
+ const requiredTemplateIds = getRequiredTemplateIdSet(targets, request.profile);
1566
2248
  return {
1567
2249
  ok: true,
1568
2250
  files: files.sort(compareGeneratedFiles),
1569
- templates: templates.filter((template) => targets.includes(template.target)).sort(compareTemplates)
2251
+ templates: templates.filter((template) => requiredTemplateIds.has(template.id)).sort(compareTemplates)
1570
2252
  };
1571
2253
  }
2254
+ function mergeTemplates(base, extra) {
2255
+ const seen = new Set(base.map((template) => template.id));
2256
+ const result = [...base];
2257
+ for (const template of extra) {
2258
+ if (!seen.has(template.id)) {
2259
+ seen.add(template.id);
2260
+ result.push(template);
2261
+ }
2262
+ }
2263
+ return result.sort(compareTemplates);
2264
+ }
2265
+ function getSubagentTemplates(profile) {
2266
+ const agents = getEnabledSubagents(profile);
2267
+ if (agents.length === 0) {
2268
+ return [];
2269
+ }
2270
+ const effective = deriveEffectivePermissions(profile);
2271
+ const templates = [];
2272
+ if (profile.clients.claude.enabled) {
2273
+ for (const agent of agents) {
2274
+ templates.push({
2275
+ id: `targets/claude-subagents/${agent.name}@1`,
2276
+ target: "claude-subagents",
2277
+ version: "1",
2278
+ sha256: sha256Hex(
2279
+ normalizeGeneratedText(renderClaudeSubagent(agent, effective))
2280
+ )
2281
+ });
2282
+ }
2283
+ }
2284
+ if (profile.clients.codex.enabled) {
2285
+ for (const agent of agents) {
2286
+ templates.push({
2287
+ id: `targets/codex-subagents/${agent.name}@1`,
2288
+ target: "codex-subagents",
2289
+ version: "1",
2290
+ sha256: sha256Hex(
2291
+ normalizeGeneratedText(renderCodexSubagent(agent, effective))
2292
+ )
2293
+ });
2294
+ }
2295
+ }
2296
+ if (profile.clients.tabnine.enabled) {
2297
+ for (const agent of agents) {
2298
+ if (agent.toolScope !== "read-only") {
2299
+ continue;
2300
+ }
2301
+ templates.push({
2302
+ id: `targets/tabnine-subagents/${agent.name}@1`,
2303
+ target: "tabnine-subagents",
2304
+ version: "1",
2305
+ sha256: sha256Hex(normalizeGeneratedText(renderTabnineSubagent(agent)))
2306
+ });
2307
+ }
2308
+ }
2309
+ return templates;
2310
+ }
1572
2311
  function renderAgentsMd(profile) {
1573
2312
  const effectivePermissions = deriveEffectivePermissions(profile);
1574
2313
  const enabledClients2 = renderEnabledClients(profile);
2314
+ const reactSection = profile.stack.frameworks.includes("react") ? `
2315
+ ${renderTopicAsAgentsMdSection(REACT_STACK_TOPIC)}` : "";
2316
+ const codeReviewSection = profile.workflow.codeReview === true ? `
2317
+ ${renderTopicAsAgentsMdSection(CODE_REVIEW_TOPIC)}` : "";
2318
+ const refactoringSection = profile.workflow.refactoring === true ? `
2319
+ ${renderTopicAsAgentsMdSection(REFACTORING_TOPIC)}` : "";
2320
+ const documentationSection = profile.workflow.documentation === true ? `
2321
+ ${renderTopicAsAgentsMdSection(DOCUMENTATION_TOPIC)}` : "";
1575
2322
  return normalizeGeneratedText(`# AGENTS.md
1576
2323
 
1577
2324
  ## Project
@@ -1586,7 +2333,7 @@ Description: ${escapeMarkdownText(profile.profile.description)}
1586
2333
  - Frameworks: ${renderList(profile.stack.frameworks)}
1587
2334
  - Package managers: ${renderList(profile.stack.packageManagers)}
1588
2335
  - Testing: ${renderList(profile.stack.testing)}
1589
-
2336
+ ${reactSection}
1590
2337
  ## Enabled AI Clients
1591
2338
 
1592
2339
  ${enabledClients2}
@@ -1596,7 +2343,7 @@ ${enabledClients2}
1596
2343
  - SDD: ${renderRequired(profile.workflow.sdd)}
1597
2344
  - TDD: ${renderRequired(profile.workflow.tdd)}
1598
2345
  - Final implementation review: ${renderRequired(profile.workflow.finalReview)}
1599
-
2346
+ ${codeReviewSection}${refactoringSection}${documentationSection}
1600
2347
  ## Permissions
1601
2348
 
1602
2349
  | Permission | Mode |
@@ -1688,9 +2435,15 @@ function renderTarget(target, profile) {
1688
2435
  ".codex/config.toml",
1689
2436
  "codex-config",
1690
2437
  "targets/codex-config@1",
1691
- renderCodexConfigToml()
2438
+ renderCodexConfigToml(profile)
1692
2439
  )
1693
2440
  ];
2441
+ case "codex-subagents":
2442
+ return renderCodexSubagentFiles(profile);
2443
+ case "claude-subagents":
2444
+ return renderClaudeSubagentFiles(profile);
2445
+ case "tabnine-subagents":
2446
+ return renderTabnineSubagentFiles(profile);
1694
2447
  case "codex-workflow-skills":
1695
2448
  return renderWorkflowSkillFiles(
1696
2449
  profile,
@@ -1774,6 +2527,16 @@ function renderTabnineGuidelines(profile) {
1774
2527
  )
1775
2528
  );
1776
2529
  }
2530
+ if (hasStack(profile, "frameworks", "react")) {
2531
+ files.push(
2532
+ createGeneratedTextFile(
2533
+ ".tabnine/guidelines/30-stack-typescript-react.md",
2534
+ common.target,
2535
+ "targets/tabnine-guidelines/30-stack-typescript-react@1",
2536
+ renderTypeScriptReactGuideline()
2537
+ )
2538
+ );
2539
+ }
1777
2540
  if (hasStack(profile, "languages", "java") && hasStack(profile, "frameworks", "spring-boot")) {
1778
2541
  files.push(
1779
2542
  createGeneratedTextFile(
@@ -1794,6 +2557,36 @@ function renderTabnineGuidelines(profile) {
1794
2557
  )
1795
2558
  );
1796
2559
  }
2560
+ if (profile.workflow.codeReview === true) {
2561
+ files.push(
2562
+ createGeneratedTextFile(
2563
+ ".tabnine/guidelines/60-code-review.md",
2564
+ common.target,
2565
+ "targets/tabnine-guidelines/60-code-review@1",
2566
+ renderCodeReviewGuideline()
2567
+ )
2568
+ );
2569
+ }
2570
+ if (profile.workflow.refactoring === true) {
2571
+ files.push(
2572
+ createGeneratedTextFile(
2573
+ ".tabnine/guidelines/70-refactoring.md",
2574
+ common.target,
2575
+ "targets/tabnine-guidelines/70-refactoring@1",
2576
+ renderRefactoringGuideline()
2577
+ )
2578
+ );
2579
+ }
2580
+ if (profile.workflow.documentation === true) {
2581
+ files.push(
2582
+ createGeneratedTextFile(
2583
+ ".tabnine/guidelines/80-documentation.md",
2584
+ common.target,
2585
+ "targets/tabnine-guidelines/80-documentation@1",
2586
+ renderDocumentationGuideline()
2587
+ )
2588
+ );
2589
+ }
1797
2590
  if (profile.workflow.finalReview) {
1798
2591
  files.push(
1799
2592
  createGeneratedTextFile(
@@ -1813,11 +2606,15 @@ function renderWorkflowSkillFiles(profile, target, rootPath) {
1813
2606
  (skill) => createGeneratedTextFile(
1814
2607
  `${rootPath}/${skill.id}/SKILL.md`,
1815
2608
  target,
1816
- `targets/${target}/${skill.id}@1`,
2609
+ getWorkflowSkillTemplateId(target, skill.id),
1817
2610
  renderWorkflowSkill(skill.id)
1818
2611
  )
1819
2612
  );
1820
2613
  }
2614
+ function getWorkflowSkillTemplateId(target, skill) {
2615
+ const version = skill === "tdd-change" ? "2" : "1";
2616
+ return `targets/${target}/${skill}@${version}`;
2617
+ }
1821
2618
  function renderWorkflowSkill(skill) {
1822
2619
  switch (skill) {
1823
2620
  case "sdd-change":
@@ -1849,7 +2646,7 @@ description: Use when implementing a meaningful repository change that requires
1849
2646
  case "tdd-change":
1850
2647
  return `---
1851
2648
  name: tdd-change
1852
- description: Use when changing behavior where a focused failing test or golden fixture should lead the implementation.
2649
+ description: Use when changing behavior where a focused failing test or golden fixture must prove RED before implementation and GREEN after the minimal fix.
1853
2650
  ---
1854
2651
 
1855
2652
  <!-- Generated by Agent Profile Compiler. Do not edit by hand. -->
@@ -1859,11 +2656,24 @@ description: Use when changing behavior where a focused failing test or golden f
1859
2656
  ## Instructions
1860
2657
 
1861
2658
  1. Identify the smallest observable behavior covered by the approved spec.
1862
- 2. Add or update the focused failing test first when practical.
1863
- 3. For generated outputs, add or review the golden fixture intentionally.
1864
- 4. Implement the smallest change that satisfies the test and spec.
1865
- 5. Run the relevant test command and report any test that cannot be run.
1866
- 6. Do not update golden files only to hide an unexplained behavior change.
2659
+ 2. Add or update one focused failing test or golden fixture before changing behavior code.
2660
+ 3. Run the narrowest relevant test command and confirm RED: the test fails for the expected reason, not because of a typo, setup error, or unrelated failure.
2661
+ 4. Implement the smallest change that satisfies the failing test and the spec.
2662
+ 5. Run the same focused test command and confirm GREEN: the test passes without new warnings or unrelated failures.
2663
+ 6. Refactor only after GREEN, then rerun the focused test command.
2664
+ 7. Do not update golden files only to hide an unexplained behavior change.
2665
+
2666
+ ## Testing Anti-Patterns
2667
+
2668
+ - Do not assert on mock elements or mock call counts when a real behavior assertion is possible.
2669
+ - Do not add production methods, flags, or exports that exist only for tests.
2670
+ - Do not mock a dependency until you understand the side effects the test needs.
2671
+ - Keep test doubles structurally complete enough to match the real data shape consumed by the code.
2672
+ - If mock setup is larger than the behavior under test, consider a narrower integration test or a simpler production boundary.
2673
+
2674
+ ## Output
2675
+
2676
+ Report the RED command and expected failure, the GREEN command and passing result, any refactor rerun, and any TDD exception that required human approval.
1867
2677
 
1868
2678
  ## Safety
1869
2679
 
@@ -1893,6 +2703,56 @@ description: Use before handing off an implementation to compare the diff agains
1893
2703
  ## Output
1894
2704
 
1895
2705
  Return a concise final review with spec compliance, tests run, contract impact, security impact, and remaining risks.
2706
+ `;
2707
+ case "subagent-driven-change":
2708
+ return `---
2709
+ name: subagent-driven-change
2710
+ description: Use when a scoped implementation can be delegated to an implementation subagent and then independently reviewed for spec compliance before code quality.
2711
+ ---
2712
+
2713
+ <!-- Generated by Agent Profile Compiler. Do not edit by hand. -->
2714
+
2715
+ # Subagent-Driven Change
2716
+
2717
+ ## Preconditions
2718
+
2719
+ Use this workflow only when the task has a clear spec, acceptance criteria, and file ownership. Keep tightly coupled or architectural decisions in the parent session unless the user explicitly asks for delegation.
2720
+
2721
+ Required subagents: \`implementer\`, \`spec-reviewer\`, and \`code-quality-reviewer\`.
2722
+
2723
+ ## Fresh Context
2724
+
2725
+ Each subagent prompt must include the full task text, relevant spec excerpts, non-goals, acceptance criteria, file ownership, constraints, expected tests, and any command limits. Do not rely on hidden chat history or a previous subagent's memory.
2726
+
2727
+ ## Flow
2728
+
2729
+ 1. Dispatch \`implementer\` with one bounded task and the complete context it needs.
2730
+ 2. If \`implementer\` returns \`BLOCKED\` or \`NEEDS_CONTEXT\`, resolve that before continuing.
2731
+ 3. If \`implementer\` returns \`DONE_WITH_CONCERNS\`, read the concerns before review and decide whether to fix, narrow scope, or continue.
2732
+ 4. Dispatch \`spec-reviewer\` with the original task, relevant spec excerpts, changed files, and implementer report.
2733
+ 5. Fix or escalate every spec-review issue before requesting code-quality review.
2734
+ 6. Dispatch \`code-quality-reviewer\` only after spec review reports compliance.
2735
+ 7. Fix Critical and Important code-quality issues before handoff, or document why a finding is intentionally deferred.
2736
+ 8. Run the relevant tests, golden tests, and doctor/check commands required by the spec before final response.
2737
+
2738
+ ## Status Values
2739
+
2740
+ Implementation worker status values: \`DONE\`, \`DONE_WITH_CONCERNS\`, \`BLOCKED\`, \`NEEDS_CONTEXT\`.
2741
+
2742
+ Spec reviewer status values: \`COMPLIANT\`, \`ISSUES_FOUND\`, \`NEEDS_CONTEXT\`.
2743
+
2744
+ Code-quality reviewer status values: \`ACCEPTABLE\`, \`ISSUES_FOUND\`, \`NEEDS_CONTEXT\`.
2745
+
2746
+ ## Safety
2747
+
2748
+ - Do not ask subagents to commit, push, create branches, install dependencies, read secrets, contact production systems, or upload source unless the user explicitly requested that action.
2749
+ - Do not accept an implementation report without reading reviewer findings.
2750
+ - Do not run code-quality review before spec-compliance review passes.
2751
+ - Keep generated files deterministic and lockfile-tracked.
2752
+
2753
+ ## Output
2754
+
2755
+ Final handoff must list what changed, tests run, contract impact, security impact, remaining risks or TODOs, and whether the spec acceptance criteria are fully met.
1896
2756
  `;
1897
2757
  }
1898
2758
  }
@@ -1958,6 +2818,18 @@ Test-driven development is required for this project.
1958
2818
  - Do not update golden fixtures to hide an unexplained behavior change.
1959
2819
  `;
1960
2820
  }
2821
+ function renderTypeScriptReactGuideline() {
2822
+ return renderTopicAsTabnineGuideline(REACT_STACK_TOPIC);
2823
+ }
2824
+ function renderCodeReviewGuideline() {
2825
+ return renderTopicAsTabnineGuideline(CODE_REVIEW_TOPIC);
2826
+ }
2827
+ function renderRefactoringGuideline() {
2828
+ return renderTopicAsTabnineGuideline(REFACTORING_TOPIC);
2829
+ }
2830
+ function renderDocumentationGuideline() {
2831
+ return renderTopicAsTabnineGuideline(DOCUMENTATION_TOPIC);
2832
+ }
1961
2833
  function renderTypeScriptSvelteGuideline() {
1962
2834
  return `<!-- Generated by Agent Profile Compiler. Do not edit by hand. -->
1963
2835
 
@@ -2016,14 +2888,165 @@ Final implementation review is required for this project.
2016
2888
  - List remaining risks or TODOs before considering the task complete.
2017
2889
  `;
2018
2890
  }
2019
- function renderCodexConfigToml() {
2020
- return `approval_policy = "on-request"
2891
+ function renderCodexConfigToml(profile) {
2892
+ const base = `approval_policy = "on-request"
2021
2893
  sandbox_mode = "workspace-write"
2022
2894
  allow_login_shell = false
2023
2895
 
2024
2896
  [sandbox_workspace_write]
2025
2897
  network_access = false
2026
2898
  `;
2899
+ if (!profile || getEnabledSubagents(profile).length === 0) {
2900
+ return base;
2901
+ }
2902
+ const defaults = getSubagentDefaults(profile);
2903
+ return `${base}
2904
+ [agents]
2905
+ max_threads = ${defaults.maxConcurrent}
2906
+ max_depth = ${defaults.maxDepth}
2907
+ `;
2908
+ }
2909
+ function renderClaudeSubagentFiles(profile) {
2910
+ const agents = getEnabledSubagents(profile);
2911
+ const effective = deriveEffectivePermissions(profile);
2912
+ return agents.map(
2913
+ (agent) => createGeneratedTextFile(
2914
+ `.claude/agents/${agent.name}.md`,
2915
+ "claude-subagents",
2916
+ `targets/claude-subagents/${agent.name}@1`,
2917
+ renderClaudeSubagent(agent, effective)
2918
+ )
2919
+ );
2920
+ }
2921
+ function renderCodexSubagentFiles(profile) {
2922
+ const agents = getEnabledSubagents(profile);
2923
+ const effective = deriveEffectivePermissions(profile);
2924
+ return agents.map(
2925
+ (agent) => createGeneratedTextFile(
2926
+ `.codex/agents/${agent.name}.toml`,
2927
+ "codex-subagents",
2928
+ `targets/codex-subagents/${agent.name}@1`,
2929
+ renderCodexSubagent(agent, effective)
2930
+ )
2931
+ );
2932
+ }
2933
+ function renderTabnineSubagentFiles(profile) {
2934
+ const agents = getEnabledSubagents(profile).filter(
2935
+ (agent) => agent.toolScope === "read-only"
2936
+ );
2937
+ return agents.map(
2938
+ (agent) => createGeneratedTextFile(
2939
+ `.tabnine/agent/agents/${agent.name}.md`,
2940
+ "tabnine-subagents",
2941
+ `targets/tabnine-subagents/${agent.name}@1`,
2942
+ renderTabnineSubagent(agent)
2943
+ )
2944
+ );
2945
+ }
2946
+ function titleCaseFromKebab(name) {
2947
+ return name.split("-").filter((part) => part.length > 0).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
2948
+ }
2949
+ function renderClaudeSubagent(agent, effective) {
2950
+ const lines = ["---", `name: ${yamlScalarSafe(agent.name)}`];
2951
+ lines.push(`description: ${yamlScalarSafe(agent.description)}`);
2952
+ if (agent.toolScope === "read-only") {
2953
+ lines.push("tools: Read, Glob, Grep");
2954
+ } else {
2955
+ const tools = ["Read", "Glob", "Grep"];
2956
+ if (effective.filesystem.write === "allow") {
2957
+ tools.push("Edit", "Write");
2958
+ }
2959
+ if (effective.shell.run === "allow") {
2960
+ tools.push("Bash");
2961
+ }
2962
+ if (effective.network.external === "allow") {
2963
+ tools.push("WebFetch");
2964
+ }
2965
+ lines.push(`tools: ${tools.join(", ")}`);
2966
+ }
2967
+ const model = agent.modelPreference ?? "inherit";
2968
+ lines.push(`model: ${model === "inherit" ? "inherit" : "inherit"}`);
2969
+ if (agent.toolScope === "read-only") {
2970
+ lines.push("permissionMode: plan");
2971
+ }
2972
+ if (agent.maxTurns !== void 0) {
2973
+ lines.push(`maxTurns: ${agent.maxTurns}`);
2974
+ }
2975
+ lines.push("---", "");
2976
+ lines.push("<!-- Generated by Agent Profile Compiler. Do not edit by hand. -->");
2977
+ lines.push("");
2978
+ lines.push(`# ${titleCaseFromKebab(agent.name)}`);
2979
+ lines.push("");
2980
+ lines.push(agent.prompt.trim());
2981
+ lines.push("");
2982
+ return lines.join("\n");
2983
+ }
2984
+ function renderCodexSubagent(agent, effective) {
2985
+ const lines = [
2986
+ "# Generated by Agent Profile Compiler. Do not edit by hand.",
2987
+ "",
2988
+ `name = "${escapeTomlString(agent.name)}"`,
2989
+ `description = "${escapeTomlString(agent.description)}"`
2990
+ ];
2991
+ if (agent.toolScope === "workspace-write" && effective.filesystem.write !== "deny") {
2992
+ lines.push(`sandbox_mode = "workspace-write"`);
2993
+ } else {
2994
+ lines.push(`sandbox_mode = "read-only"`);
2995
+ }
2996
+ if (agent.prompt.includes(`"""`)) {
2997
+ throw new Error(
2998
+ `Codex subagent ${agent.name} prompt contains a TOML triple-quote sequence; compile validation should have rejected this.`
2999
+ );
3000
+ }
3001
+ lines.push('developer_instructions = """');
3002
+ for (const promptLine of agent.prompt.trim().split(/\r?\n/u)) {
3003
+ lines.push(promptLine);
3004
+ }
3005
+ lines.push('"""');
3006
+ lines.push("");
3007
+ return lines.join("\n");
3008
+ }
3009
+ function renderTabnineSubagent(agent) {
3010
+ const lines = ["---", `name: ${yamlScalarSafe(agent.name)}`];
3011
+ lines.push(`description: ${yamlScalarSafe(agent.description)}`);
3012
+ lines.push("kind: local");
3013
+ lines.push("tools:");
3014
+ for (const tool of ["grep_search", "read_file"].sort()) {
3015
+ lines.push(` - ${tool}`);
3016
+ }
3017
+ if (agent.maxTurns !== void 0) {
3018
+ lines.push(`max_turns: ${agent.maxTurns}`);
3019
+ }
3020
+ if (agent.timeoutMinutes !== void 0) {
3021
+ lines.push(`timeout_mins: ${agent.timeoutMinutes}`);
3022
+ }
3023
+ lines.push("---", "");
3024
+ lines.push("<!-- Generated by Agent Profile Compiler. Do not edit by hand. -->");
3025
+ lines.push("");
3026
+ lines.push(`# ${titleCaseFromKebab(agent.name)}`);
3027
+ lines.push("");
3028
+ lines.push(agent.prompt.trim());
3029
+ lines.push("");
3030
+ return lines.join("\n");
3031
+ }
3032
+ function escapeTomlString(value) {
3033
+ return value.replace(/\\/gu, "\\\\").replace(/"/gu, '\\"');
3034
+ }
3035
+ function yamlScalarSafe(value) {
3036
+ if (yamlScalarNeedsQuoting(value)) {
3037
+ return JSON.stringify(value);
3038
+ }
3039
+ return value;
3040
+ }
3041
+ function yamlScalarNeedsQuoting(value) {
3042
+ if (value.length === 0) return true;
3043
+ if (/[\r\n\t]/u.test(value)) return true;
3044
+ if (/^\s|\s$/u.test(value)) return true;
3045
+ if (/^[\-?:,\[\]{}#&*!|>'"%@`]/u.test(value)) return true;
3046
+ if (value.includes(": ") || value.includes(" #")) return true;
3047
+ if (/^(?:true|false|null|yes|no|on|off|~)$/iu.test(value)) return true;
3048
+ if (/^[+\-]?\d+(?:\.\d+)?(?:[eE][+\-]?\d+)?$/u.test(value)) return true;
3049
+ return false;
2027
3050
  }
2028
3051
  function renderAgentsMdTemplateSource() {
2029
3052
  return `# AGENTS.md
@@ -2062,6 +3085,14 @@ function guidelineTemplateSource(id, render) {
2062
3085
  source: render()
2063
3086
  };
2064
3087
  }
3088
+ function agentsMdTopicTemplateSource(id, topic) {
3089
+ return {
3090
+ id,
3091
+ target: "agents-md",
3092
+ version: "1",
3093
+ source: renderTopicAsAgentsMdSection(topic)
3094
+ };
3095
+ }
2065
3096
  function workflowSkillTemplateSource(id, target, skill) {
2066
3097
  return {
2067
3098
  id,
@@ -2100,11 +3131,18 @@ function renderClaudeSettingsJson() {
2100
3131
  }
2101
3132
  function getEnabledTargetIds(profile) {
2102
3133
  const targets = ["agents-md"];
3134
+ const subagentsEnabled = getEnabledSubagents(profile).length > 0;
2103
3135
  if (profile.clients.tabnine.enabled) {
2104
3136
  targets.push("tabnine-guidelines", "tabnine-mcp-config");
3137
+ if (subagentsEnabled) {
3138
+ targets.push("tabnine-subagents");
3139
+ }
2105
3140
  }
2106
3141
  if (profile.clients.codex.enabled) {
2107
3142
  targets.push("codex-config", "codex-workflow-skills");
3143
+ if (subagentsEnabled) {
3144
+ targets.push("codex-subagents");
3145
+ }
2108
3146
  }
2109
3147
  if (profile.clients.claude.enabled) {
2110
3148
  targets.push(
@@ -2113,6 +3151,9 @@ function getEnabledTargetIds(profile) {
2113
3151
  "claude-md",
2114
3152
  "claude-workflow-skills"
2115
3153
  );
3154
+ if (subagentsEnabled) {
3155
+ targets.push("claude-subagents");
3156
+ }
2116
3157
  }
2117
3158
  return targets;
2118
3159
  }
@@ -2121,6 +3162,70 @@ function validateTargets(profile, targets, templates) {
2121
3162
  const templateIds = new Set(templates.map((template) => template.id));
2122
3163
  const enabledTargets = new Set(getEnabledTargetIds(profile));
2123
3164
  const issues = [];
3165
+ if (profile.workflow.subagentDrivenDevelopment === true) {
3166
+ const refs = new Set(getSubagentTemplateRefs(profile));
3167
+ const missing = SUBAGENT_TEMPLATE_NAMES.filter((name) => !refs.has(name));
3168
+ if (missing.length > 0) {
3169
+ issues.push({
3170
+ code: "missing_required_template_reference",
3171
+ path: "/workflow/subagentDrivenDevelopment",
3172
+ expected: `useTemplate references for ${SUBAGENT_TEMPLATE_NAMES.join(", ")}`,
3173
+ actual: `missing ${missing.join(", ")}`,
3174
+ message: `workflow.subagentDrivenDevelopment requires capabilities.delegation.subagents.agents[].useTemplate references for ${missing.join(", ")}.`
3175
+ });
3176
+ }
3177
+ }
3178
+ if (targets.includes("tabnine-subagents")) {
3179
+ const writeAgents = getEnabledSubagents(profile).filter(
3180
+ (agent) => agent.toolScope === "workspace-write"
3181
+ );
3182
+ for (const agent of writeAgents) {
3183
+ issues.push({
3184
+ code: "unsafe_generated_content",
3185
+ path: `.tabnine/agent/agents/${agent.name}.md`,
3186
+ expected: "read-only Tabnine subagent",
3187
+ actual: "workspace-write Tabnine subagent",
3188
+ message: `tabnine-subagents cannot emit workspace-write agent ${agent.name} while Tabnine subagents are experimental/no-confirmation.`
3189
+ });
3190
+ }
3191
+ }
3192
+ if (targets.includes("codex-subagents")) {
3193
+ const effective = deriveEffectivePermissions(profile);
3194
+ for (const agent of getEnabledSubagents(profile)) {
3195
+ if (agent.toolScope === "workspace-write" && effective.filesystem.write === "deny") {
3196
+ issues.push({
3197
+ code: "unsafe_generated_content",
3198
+ path: `.codex/agents/${agent.name}.toml`,
3199
+ expected: "narrower than effectivePermissions",
3200
+ actual: "workspace-write subagent under filesystem.write=deny",
3201
+ message: `codex-subagents cannot emit workspace-write agent ${agent.name} because effectivePermissions.filesystem.write is deny.`
3202
+ });
3203
+ }
3204
+ if (agent.prompt.includes(`"""`)) {
3205
+ issues.push({
3206
+ code: "unsafe_generated_content",
3207
+ path: `.codex/agents/${agent.name}.toml`,
3208
+ expected: "prompt without TOML triple-quote sequence",
3209
+ actual: 'prompt contains """ delimiter',
3210
+ message: `codex-subagents prompt for ${agent.name} contains a TOML triple-quote sequence that would break developer_instructions.`
3211
+ });
3212
+ }
3213
+ }
3214
+ }
3215
+ if (targets.includes("claude-subagents")) {
3216
+ const effective = deriveEffectivePermissions(profile);
3217
+ for (const agent of getEnabledSubagents(profile)) {
3218
+ if (agent.toolScope === "workspace-write" && effective.filesystem.write === "deny") {
3219
+ issues.push({
3220
+ code: "unsafe_generated_content",
3221
+ path: `.claude/agents/${agent.name}.md`,
3222
+ expected: "narrower than effectivePermissions",
3223
+ actual: "workspace-write subagent under filesystem.write=deny",
3224
+ message: `claude-subagents cannot emit workspace-write agent ${agent.name} because effectivePermissions.filesystem.write is deny.`
3225
+ });
3226
+ }
3227
+ }
3228
+ }
2124
3229
  for (const target of targets) {
2125
3230
  if (!supportedTargets.has(target)) {
2126
3231
  issues.push({
@@ -2142,7 +3247,7 @@ function validateTargets(profile, targets, templates) {
2142
3247
  });
2143
3248
  continue;
2144
3249
  }
2145
- for (const templateId of getRequiredTemplateIds(target)) {
3250
+ for (const templateId of getRequiredTemplateIds(target, profile)) {
2146
3251
  if (!templateIds.has(templateId)) {
2147
3252
  issues.push({
2148
3253
  code: "missing_template",
@@ -2156,11 +3261,101 @@ function validateTargets(profile, targets, templates) {
2156
3261
  }
2157
3262
  return issues;
2158
3263
  }
2159
- function getRequiredTemplateIds(target) {
2160
- return TEMPLATE_SOURCES.filter((template) => template.target === target).map(
2161
- (template) => template.id
3264
+ function getRequiredTemplateIdSet(targets, profile) {
3265
+ return new Set(
3266
+ targets.flatMap((target) => getRequiredTemplateIds(target, profile))
2162
3267
  );
2163
3268
  }
3269
+ function getRequiredTemplateIds(target, profile) {
3270
+ switch (target) {
3271
+ case "agents-md":
3272
+ return [
3273
+ "targets/agents-md@1",
3274
+ ...getRequiredAgentsMdTopicTemplateIds(profile)
3275
+ ];
3276
+ case "tabnine-guidelines":
3277
+ return getRequiredTabnineGuidelineTemplateIds(profile);
3278
+ case "codex-workflow-skills":
3279
+ return getRequiredWorkflowSkillTemplateIds(
3280
+ profile,
3281
+ "codex-workflow-skills"
3282
+ );
3283
+ case "claude-workflow-skills":
3284
+ return getRequiredWorkflowSkillTemplateIds(
3285
+ profile,
3286
+ "claude-workflow-skills"
3287
+ );
3288
+ case "claude-subagents":
3289
+ return getEnabledSubagents(profile).map(
3290
+ (agent) => `targets/claude-subagents/${agent.name}@1`
3291
+ );
3292
+ case "codex-subagents":
3293
+ return getEnabledSubagents(profile).map(
3294
+ (agent) => `targets/codex-subagents/${agent.name}@1`
3295
+ );
3296
+ case "tabnine-subagents":
3297
+ return getEnabledSubagents(profile).filter((agent) => agent.toolScope === "read-only").map((agent) => `targets/tabnine-subagents/${agent.name}@1`);
3298
+ default:
3299
+ return TEMPLATE_SOURCES.filter(
3300
+ (template) => template.target === target
3301
+ ).map((template) => template.id);
3302
+ }
3303
+ }
3304
+ function getRequiredAgentsMdTopicTemplateIds(profile) {
3305
+ const ids = [];
3306
+ if (hasStack(profile, "frameworks", "react")) {
3307
+ ids.push("targets/agents-md/30-stack-typescript-react@1");
3308
+ }
3309
+ if (profile.workflow.codeReview === true) {
3310
+ ids.push("targets/agents-md/60-code-review@1");
3311
+ }
3312
+ if (profile.workflow.refactoring === true) {
3313
+ ids.push("targets/agents-md/70-refactoring@1");
3314
+ }
3315
+ if (profile.workflow.documentation === true) {
3316
+ ids.push("targets/agents-md/80-documentation@1");
3317
+ }
3318
+ return ids;
3319
+ }
3320
+ function getRequiredTabnineGuidelineTemplateIds(profile) {
3321
+ const ids = ["targets/tabnine-guidelines/00-general-agent-behavior@1"];
3322
+ if (profile.workflow.sdd) {
3323
+ ids.push("targets/tabnine-guidelines/10-sdd-workflow@1");
3324
+ }
3325
+ if (profile.workflow.tdd) {
3326
+ ids.push("targets/tabnine-guidelines/20-tdd-workflow@1");
3327
+ }
3328
+ if (hasStack(profile, "languages", "typescript") && hasStack(profile, "frameworks", "sveltekit")) {
3329
+ ids.push("targets/tabnine-guidelines/30-stack-typescript-svelte@1");
3330
+ }
3331
+ if (hasStack(profile, "frameworks", "react")) {
3332
+ ids.push("targets/tabnine-guidelines/30-stack-typescript-react@1");
3333
+ }
3334
+ if (hasStack(profile, "languages", "java") && hasStack(profile, "frameworks", "spring-boot")) {
3335
+ ids.push("targets/tabnine-guidelines/40-stack-java-spring@1");
3336
+ }
3337
+ if (hasAnyStack(profile, "testing", ["playwright", "junit"])) {
3338
+ ids.push("targets/tabnine-guidelines/50-testing-playwright-junit@1");
3339
+ }
3340
+ if (profile.workflow.codeReview === true) {
3341
+ ids.push("targets/tabnine-guidelines/60-code-review@1");
3342
+ }
3343
+ if (profile.workflow.refactoring === true) {
3344
+ ids.push("targets/tabnine-guidelines/70-refactoring@1");
3345
+ }
3346
+ if (profile.workflow.documentation === true) {
3347
+ ids.push("targets/tabnine-guidelines/80-documentation@1");
3348
+ }
3349
+ if (profile.workflow.finalReview) {
3350
+ ids.push("targets/tabnine-guidelines/90-final-review@1");
3351
+ }
3352
+ return ids;
3353
+ }
3354
+ function getRequiredWorkflowSkillTemplateIds(profile, target) {
3355
+ return WORKFLOW_SKILLS.filter(
3356
+ (skill) => profile.workflow[skill.workflowFlag]
3357
+ ).map((skill) => getWorkflowSkillTemplateId(target, skill.id));
3358
+ }
2164
3359
  function uniqueTargets(targets) {
2165
3360
  return Array.from(new Set(targets));
2166
3361
  }
@@ -2331,6 +3526,7 @@ function isNodeError(error) {
2331
3526
  // ../../packages/scanner/src/stack.ts
2332
3527
  import fsPromises2 from "node:fs/promises";
2333
3528
  import path2 from "node:path";
3529
+ import { parse as parseYaml } from "yaml";
2334
3530
  var EMPTY_STACK = {
2335
3531
  languages: [],
2336
3532
  frameworks: [],
@@ -2391,6 +3587,9 @@ async function detectStack(rootDir) {
2391
3587
  if (await anyFileExists(rootPath, PLAYWRIGHT_CONFIGS)) {
2392
3588
  stack.testing.push("playwright");
2393
3589
  }
3590
+ if (await fileExists(rootPath, "pubspec.yaml")) {
3591
+ await detectPubspecYaml(rootPath, stack, warnings);
3592
+ }
2394
3593
  return {
2395
3594
  stack: sortStack(stack),
2396
3595
  warnings: warnings.sort(compareWarnings)
@@ -2450,6 +3649,118 @@ async function detectPackageJson(rootPath, stack, warnings) {
2450
3649
  }
2451
3650
  }
2452
3651
  }
3652
+ var RIVERPOD_PACKAGES = /* @__PURE__ */ new Set([
3653
+ "riverpod",
3654
+ "flutter_riverpod",
3655
+ "hooks_riverpod",
3656
+ "riverpod_annotation",
3657
+ "riverpod_generator"
3658
+ ]);
3659
+ var DRIFT_PACKAGES = /* @__PURE__ */ new Set(["drift", "drift_flutter", "drift_dev"]);
3660
+ var DOTLOTTIE_PACKAGES = /* @__PURE__ */ new Set([
3661
+ "dotlottie_loader",
3662
+ "dotlottie_flutter"
3663
+ ]);
3664
+ var FIREBASE_PACKAGES = /* @__PURE__ */ new Set([
3665
+ "cloud_firestore",
3666
+ "cloud_functions",
3667
+ "firebase_ai",
3668
+ "firebase_analytics",
3669
+ "firebase_app_check",
3670
+ "firebase_app_installations",
3671
+ "firebase_auth",
3672
+ "firebase_core",
3673
+ "firebase_crashlytics",
3674
+ "firebase_data_connect",
3675
+ "firebase_database",
3676
+ "firebase_dynamic_links",
3677
+ "firebase_in_app_messaging",
3678
+ "firebase_messaging",
3679
+ "firebase_ml_model_downloader",
3680
+ "firebase_performance",
3681
+ "firebase_remote_config",
3682
+ "firebase_storage",
3683
+ "firebase_vertexai"
3684
+ ]);
3685
+ async function detectPubspecYaml(rootPath, stack, warnings) {
3686
+ let value;
3687
+ try {
3688
+ value = parseYaml(
3689
+ await fsPromises2.readFile(path2.join(rootPath, "pubspec.yaml"), "utf8")
3690
+ );
3691
+ } catch {
3692
+ warnings.push({
3693
+ code: "metadata_parse_error",
3694
+ path: "pubspec.yaml",
3695
+ expected: "valid YAML metadata",
3696
+ actual: "parse error",
3697
+ message: "pubspec.yaml could not be parsed for stack detection."
3698
+ });
3699
+ return;
3700
+ }
3701
+ const record = getRecord(value);
3702
+ if (!record) {
3703
+ warnings.push({
3704
+ code: "metadata_parse_error",
3705
+ path: "pubspec.yaml",
3706
+ expected: "object metadata",
3707
+ actual: describeValue3(value),
3708
+ message: "pubspec.yaml does not contain object metadata."
3709
+ });
3710
+ return;
3711
+ }
3712
+ stack.packageManagers.push("pub");
3713
+ const environment = getRecord(record.environment);
3714
+ if (environment && environment.sdk !== void 0) {
3715
+ stack.languages.push("dart");
3716
+ }
3717
+ const dependencyKeys = /* @__PURE__ */ new Set();
3718
+ for (const key of ["dependencies", "dev_dependencies", "dependency_overrides"]) {
3719
+ const map = getRecord(record[key]);
3720
+ if (!map) {
3721
+ continue;
3722
+ }
3723
+ for (const dependencyKey of Object.keys(map)) {
3724
+ dependencyKeys.add(dependencyKey);
3725
+ }
3726
+ }
3727
+ if (dependencyKeys.has("flutter")) {
3728
+ stack.languages.push("dart");
3729
+ stack.frameworks.push("flutter");
3730
+ }
3731
+ if (dependencyKeys.has("flutter_test")) {
3732
+ stack.testing.push("flutter-test");
3733
+ }
3734
+ if (hasAny(dependencyKeys, RIVERPOD_PACKAGES)) {
3735
+ stack.frameworks.push("riverpod");
3736
+ }
3737
+ if (dependencyKeys.has("go_router")) {
3738
+ stack.frameworks.push("go-router");
3739
+ }
3740
+ if (hasAny(dependencyKeys, DRIFT_PACKAGES)) {
3741
+ stack.frameworks.push("drift");
3742
+ }
3743
+ if (hasAny(dependencyKeys, FIREBASE_PACKAGES)) {
3744
+ stack.frameworks.push("firebase");
3745
+ }
3746
+ if (dependencyKeys.has("rive")) {
3747
+ stack.frameworks.push("rive");
3748
+ }
3749
+ if (dependencyKeys.has("lottie")) {
3750
+ stack.frameworks.push("lottie");
3751
+ }
3752
+ if (hasAny(dependencyKeys, DOTLOTTIE_PACKAGES)) {
3753
+ stack.frameworks.push("dotlottie");
3754
+ }
3755
+ }
3756
+ function hasAny(keys, allowlist) {
3757
+ for (const allowed of allowlist) {
3758
+ if (keys.has(allowed)) {
3759
+ return true;
3760
+ }
3761
+ }
3762
+ return false;
3763
+ }
2453
3764
  async function detectJavaMetadata(rootPath, relativePath, stack) {
2454
3765
  const source = await fsPromises2.readFile(
2455
3766
  path2.join(rootPath, relativePath),
@@ -2813,6 +4124,14 @@ async function runDoctor(request = {}) {
2813
4124
  });
2814
4125
  }
2815
4126
  await checkPermissionPosture(rootDir, profileResult.profile, issues);
4127
+ await checkSubagentArtifacts({
4128
+ rootDir,
4129
+ profile: profileResult.profile,
4130
+ effective: deriveEffectivePermissions(profileResult.profile),
4131
+ generatedFiles: compileResult.files,
4132
+ lockfile,
4133
+ issues
4134
+ });
2816
4135
  return toResult(issues);
2817
4136
  }
2818
4137
  async function checkGeneratedArtifactsExist(rootDir, files, issues) {
@@ -3802,6 +5121,320 @@ function reportRuntimeUnverifiable(profile, issues) {
3802
5121
  );
3803
5122
  }
3804
5123
  }
5124
+ var SUBAGENT_ROOTS = [
5125
+ ".claude/agents",
5126
+ ".codex/agents",
5127
+ ".tabnine/agent/agents"
5128
+ ];
5129
+ var GENERATED_HEADER_MARKER = "Generated by Agent Profile Compiler";
5130
+ var TABNINE_UNSAFE_TOOL_NAMES = /* @__PURE__ */ new Set([
5131
+ "run_shell_command",
5132
+ "write_file",
5133
+ "browser_agent",
5134
+ "browser",
5135
+ "open_url",
5136
+ "fetch_url",
5137
+ "http_request",
5138
+ "network"
5139
+ ]);
5140
+ var CLAUDE_WRITE_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "NotebookEdit"]);
5141
+ async function checkSubagentArtifacts(input) {
5142
+ const { rootDir, profile, effective, generatedFiles, lockfile, issues } = input;
5143
+ const enabled = getEnabledSubagents(profile);
5144
+ const generatedSubagentPaths = new Set(
5145
+ generatedFiles.filter(
5146
+ (file) => file.target === "claude-subagents" || file.target === "codex-subagents" || file.target === "tabnine-subagents"
5147
+ ).map((file) => file.path)
5148
+ );
5149
+ const lockfileSubagentPaths = new Set(
5150
+ (lockfile?.outputs ?? []).filter(
5151
+ (output) => output.target === "claude-subagents" || output.target === "codex-subagents" || output.target === "tabnine-subagents"
5152
+ ).map((output) => output.path)
5153
+ );
5154
+ for (const agent of enabled) {
5155
+ if (isSubagentBuiltinNameCollision(agent.name)) {
5156
+ issues.push(
5157
+ issue(
5158
+ "LINT-SUBAGENT-005",
5159
+ "warning",
5160
+ `/capabilities/delegation/subagents/agents/${agent.name}`,
5161
+ "name distinct from built-ins",
5162
+ "collides with built-in after normalization",
5163
+ `Subagent ${agent.name} collides with a documented built-in after hyphen/underscore normalization.`,
5164
+ "Rename the subagent to avoid shadowing client built-ins."
5165
+ )
5166
+ );
5167
+ }
5168
+ }
5169
+ for (const root of SUBAGENT_ROOTS) {
5170
+ const files = await collectSubagentFilesUnder(rootDir, root);
5171
+ for (const relativePath of files) {
5172
+ const file = await readKnownFile(rootDir, relativePath);
5173
+ if (!file.ok) continue;
5174
+ const text = decodeUtf8(file.bytes);
5175
+ const isGenerated = text.includes(GENERATED_HEADER_MARKER);
5176
+ if (containsSecretLikeLiteral(text)) {
5177
+ issues.push(
5178
+ issue(
5179
+ "LINT-SUBAGENT-002",
5180
+ "error",
5181
+ relativePath,
5182
+ "no literal secret-like values",
5183
+ "secret-like value present",
5184
+ `${relativePath} contains a literal secret-like value.`,
5185
+ "Remove literal secrets from the subagent file; use environment variable references only."
5186
+ )
5187
+ );
5188
+ }
5189
+ if (root === ".codex/agents") {
5190
+ checkCodexSubagentFile(relativePath, text, effective, issues);
5191
+ } else if (root === ".claude/agents") {
5192
+ checkClaudeSubagentFile(relativePath, text, effective, issues);
5193
+ } else if (root === ".tabnine/agent/agents") {
5194
+ checkTabnineSubagentFile(relativePath, text, effective, issues);
5195
+ }
5196
+ if (isGenerated && !generatedSubagentPaths.has(relativePath) && !lockfileSubagentPaths.has(relativePath)) {
5197
+ issues.push(
5198
+ issue(
5199
+ "LINT-SUBAGENT-006",
5200
+ "warning",
5201
+ relativePath,
5202
+ "claimed by current compile output or lockfile",
5203
+ "orphan generated subagent",
5204
+ `${relativePath} carries the generated-file header but is not produced by the current profile and not recorded in ai-profile.lock.`,
5205
+ "Remove the orphan generated subagent file or restore the matching profile intent."
5206
+ )
5207
+ );
5208
+ }
5209
+ }
5210
+ }
5211
+ if (profile.clients.tabnine.enabled && enabled.some((agent) => agent.toolScope === "read-only")) {
5212
+ issues.push(
5213
+ issue(
5214
+ "LINT-SUBAGENT-008",
5215
+ "info",
5216
+ ".tabnine/agent/settings.json",
5217
+ "experimental.enableAgents verifiable",
5218
+ "not verifiable",
5219
+ "Tabnine experimental.enableAgents cannot be verified because Agent Profile Compiler does not write .tabnine/agent/settings.json.",
5220
+ "Manually enable experimental.enableAgents in .tabnine/agent/settings.json to use the generated Tabnine subagent files."
5221
+ )
5222
+ );
5223
+ }
5224
+ }
5225
+ function checkCodexSubagentFile(relativePath, text, effective, issues) {
5226
+ const sandboxMatch = /^\s*sandbox_mode\s*=\s*"([^"]+)"/mu.exec(text);
5227
+ const sandboxMode = sandboxMatch?.[1];
5228
+ if (sandboxMode === "danger-full-access") {
5229
+ issues.push(
5230
+ issue(
5231
+ "LINT-SUBAGENT-003",
5232
+ "error",
5233
+ relativePath,
5234
+ "sandboxed Codex subagent",
5235
+ "danger-full-access",
5236
+ `${relativePath} declares danger-full-access, which is never allowed.`,
5237
+ "Remove danger-full-access from the Codex subagent and use read-only or workspace-write instead."
5238
+ )
5239
+ );
5240
+ }
5241
+ if (sandboxMode === "workspace-write" && effective.filesystem.write === "deny") {
5242
+ issues.push(
5243
+ issue(
5244
+ "LINT-SUBAGENT-001",
5245
+ "error",
5246
+ relativePath,
5247
+ "narrower than effectivePermissions.filesystem.write=deny",
5248
+ "workspace-write",
5249
+ `${relativePath} grants workspace-write while effectivePermissions deny filesystem writes.`,
5250
+ 'Set sandbox_mode = "read-only" or tighten the subagent intent.'
5251
+ )
5252
+ );
5253
+ }
5254
+ if (/^\s*approval_policy\s*=\s*"never"/mu.test(text)) {
5255
+ issues.push(
5256
+ issue(
5257
+ "LINT-SUBAGENT-001",
5258
+ "error",
5259
+ relativePath,
5260
+ "interactive approval policy",
5261
+ "never",
5262
+ `${relativePath} declares approval_policy = "never", which is never allowed for generated Codex subagents.`,
5263
+ 'Remove approval_policy = "never" from the Codex subagent.'
5264
+ )
5265
+ );
5266
+ }
5267
+ }
5268
+ function checkClaudeSubagentFile(relativePath, text, effective, issues) {
5269
+ const frontmatter = parseMarkdownFrontmatter(text);
5270
+ if (frontmatter.permissionMode === "bypassPermissions") {
5271
+ issues.push(
5272
+ issue(
5273
+ "LINT-SUBAGENT-004",
5274
+ "error",
5275
+ relativePath,
5276
+ "non-bypass Claude permission mode",
5277
+ "bypassPermissions",
5278
+ `${relativePath} declares permissionMode: bypassPermissions, which is never allowed.`,
5279
+ "Remove bypassPermissions from the Claude subagent and use plan or default mode."
5280
+ )
5281
+ );
5282
+ }
5283
+ const tools = parseClaudeToolList(frontmatter.tools);
5284
+ if (tools.includes("Bash") && effective.shell.run !== "allow") {
5285
+ issues.push(
5286
+ issue(
5287
+ "LINT-SUBAGENT-001",
5288
+ "error",
5289
+ relativePath,
5290
+ "tools narrower than effectivePermissions.shell.run",
5291
+ "Bash tool granted",
5292
+ `${relativePath} grants the Bash tool while effectivePermissions.shell.run is ${effective.shell.run}.`,
5293
+ "Remove Bash from the Claude subagent tools list or tighten effectivePermissions."
5294
+ )
5295
+ );
5296
+ }
5297
+ if (tools.some((tool) => CLAUDE_WRITE_TOOLS.has(tool)) && effective.filesystem.write !== "allow") {
5298
+ issues.push(
5299
+ issue(
5300
+ "LINT-SUBAGENT-001",
5301
+ "error",
5302
+ relativePath,
5303
+ "tools narrower than effectivePermissions.filesystem.write",
5304
+ "Edit/Write tool granted",
5305
+ `${relativePath} grants write tools while effectivePermissions.filesystem.write is ${effective.filesystem.write}.`,
5306
+ "Remove Edit/Write from the Claude subagent tools list or tighten effectivePermissions."
5307
+ )
5308
+ );
5309
+ }
5310
+ if (tools.includes("WebFetch") && effective.network.external !== "allow") {
5311
+ issues.push(
5312
+ issue(
5313
+ "LINT-SUBAGENT-001",
5314
+ "error",
5315
+ relativePath,
5316
+ "tools narrower than effectivePermissions.network.external",
5317
+ "WebFetch tool granted",
5318
+ `${relativePath} grants the WebFetch tool while effectivePermissions.network.external is ${effective.network.external}.`,
5319
+ "Remove WebFetch from the Claude subagent tools list or tighten effectivePermissions."
5320
+ )
5321
+ );
5322
+ }
5323
+ }
5324
+ function checkTabnineSubagentFile(relativePath, text, effective, issues) {
5325
+ const frontmatter = parseMarkdownFrontmatter(text);
5326
+ const tools = parseTabnineToolList(text, frontmatter.tools);
5327
+ const unsafe = tools.filter(
5328
+ (tool) => TABNINE_UNSAFE_TOOL_NAMES.has(tool)
5329
+ );
5330
+ if (unsafe.length > 0) {
5331
+ issues.push(
5332
+ issue(
5333
+ "LINT-SUBAGENT-007",
5334
+ "warning",
5335
+ relativePath,
5336
+ "read-only Tabnine subagent tools",
5337
+ `declares ${unsafe.join(", ")}`,
5338
+ `${relativePath} declares write/shell/browser/network-capable tool ${unsafe[0]} while Tabnine subagents are experimental/no-confirmation.`,
5339
+ "Restrict generated Tabnine subagents to read-only tools until safer Tabnine semantics are documented."
5340
+ )
5341
+ );
5342
+ }
5343
+ if (tools.includes("write_file") && effective.filesystem.write !== "allow") {
5344
+ issues.push(
5345
+ issue(
5346
+ "LINT-SUBAGENT-001",
5347
+ "error",
5348
+ relativePath,
5349
+ "tools narrower than effectivePermissions.filesystem.write",
5350
+ "write_file tool granted",
5351
+ `${relativePath} grants write_file while effectivePermissions.filesystem.write is ${effective.filesystem.write}.`,
5352
+ "Remove write_file from the Tabnine subagent tools list or tighten effectivePermissions."
5353
+ )
5354
+ );
5355
+ }
5356
+ if (tools.includes("run_shell_command") && effective.shell.run !== "allow") {
5357
+ issues.push(
5358
+ issue(
5359
+ "LINT-SUBAGENT-001",
5360
+ "error",
5361
+ relativePath,
5362
+ "tools narrower than effectivePermissions.shell.run",
5363
+ "run_shell_command tool granted",
5364
+ `${relativePath} grants run_shell_command while effectivePermissions.shell.run is ${effective.shell.run}.`,
5365
+ "Remove run_shell_command from the Tabnine subagent tools list or tighten effectivePermissions."
5366
+ )
5367
+ );
5368
+ }
5369
+ }
5370
+ function parseMarkdownFrontmatter(text) {
5371
+ if (!text.startsWith("---\n")) return {};
5372
+ const end = text.indexOf("\n---", 4);
5373
+ if (end === -1) return {};
5374
+ const block = text.slice(4, end);
5375
+ const result = {};
5376
+ for (const line of block.split("\n")) {
5377
+ const match = /^([A-Za-z][A-Za-z0-9_]*)\s*:\s*(.*)$/u.exec(line);
5378
+ if (!match) continue;
5379
+ const [, key, rawValue] = match;
5380
+ if (key === void 0) continue;
5381
+ result[key] = (rawValue ?? "").trim();
5382
+ }
5383
+ return result;
5384
+ }
5385
+ function parseClaudeToolList(value) {
5386
+ if (!value) return [];
5387
+ return value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
5388
+ }
5389
+ function parseTabnineToolList(text, inlineValue) {
5390
+ if (inlineValue && inlineValue.length > 0 && inlineValue !== "") {
5391
+ if (inlineValue.startsWith("[") && inlineValue.endsWith("]")) {
5392
+ return inlineValue.slice(1, -1).split(",").map((item) => item.trim().replace(/^["']|["']$/gu, "")).filter((item) => item.length > 0);
5393
+ }
5394
+ }
5395
+ if (!text.startsWith("---\n")) return [];
5396
+ const end = text.indexOf("\n---", 4);
5397
+ if (end === -1) return [];
5398
+ const block = text.slice(4, end).split("\n");
5399
+ const tools = [];
5400
+ let inToolsBlock = false;
5401
+ for (const line of block) {
5402
+ if (/^tools\s*:\s*$/u.test(line)) {
5403
+ inToolsBlock = true;
5404
+ continue;
5405
+ }
5406
+ if (inToolsBlock) {
5407
+ const itemMatch = /^\s+-\s+(.+?)\s*$/u.exec(line);
5408
+ if (itemMatch && itemMatch[1] !== void 0) {
5409
+ tools.push(itemMatch[1].replace(/^["']|["']$/gu, ""));
5410
+ continue;
5411
+ }
5412
+ if (/^\S/u.test(line)) {
5413
+ inToolsBlock = false;
5414
+ }
5415
+ }
5416
+ }
5417
+ return tools;
5418
+ }
5419
+ async function collectSubagentFilesUnder(rootDir, relativeRoot) {
5420
+ let entries;
5421
+ try {
5422
+ entries = await readdir2(path4.join(rootDir, relativeRoot), {
5423
+ withFileTypes: true
5424
+ });
5425
+ } catch (error) {
5426
+ if (isNodeError4(error) && error.code === "ENOENT") {
5427
+ return [];
5428
+ }
5429
+ throw error;
5430
+ }
5431
+ const results = [];
5432
+ for (const entry of entries) {
5433
+ if (!entry.isFile()) continue;
5434
+ results.push(`${relativeRoot}/${entry.name}`);
5435
+ }
5436
+ return results.sort();
5437
+ }
3805
5438
  async function collectSkillFiles(rootDir) {
3806
5439
  const files = [];
3807
5440
  for (const skillRoot of SKILL_ROOTS) {
@@ -5339,7 +6972,7 @@ function formatInitRefusalFacts(input) {
5339
6972
  return [
5340
6973
  `refused: ${message}`,
5341
6974
  "schema v1 requires at least one stack.languages entry.",
5342
- "add a language manually or re-run inside a recognized project."
6975
+ "create ai-profile.yaml manually or add supported stack metadata and re-run init."
5343
6976
  ].join("\n");
5344
6977
  }
5345
6978
  return [