@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 +1656 -23
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
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@
|
|
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@
|
|
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) =>
|
|
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
|
-
|
|
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
|
|
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
|
|
1863
|
-
3.
|
|
1864
|
-
4. Implement the smallest change that satisfies the test and spec.
|
|
1865
|
-
5. Run the
|
|
1866
|
-
6.
|
|
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
|
-
|
|
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
|
|
2160
|
-
return
|
|
2161
|
-
(
|
|
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
|
-
"
|
|
6975
|
+
"create ai-profile.yaml manually or add supported stack metadata and re-run init."
|
|
5343
6976
|
].join("\n");
|
|
5344
6977
|
}
|
|
5345
6978
|
return [
|