thoth-agents 0.3.11 → 0.3.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +119 -33
  2. package/dist/{chunk-CB3EBA7H.js → chunk-5UBJB5QO.js} +1 -1
  3. package/dist/{chunk-GIUU6FNE.js → chunk-DX4U3AD3.js} +95 -50
  4. package/dist/{chunk-ZEAAWBF2.js → chunk-VPVBYBPU.js} +575 -98
  5. package/dist/cli/codex-plugin-install.d.ts +16 -2
  6. package/dist/cli/index.js +7 -5
  7. package/dist/cli/tui/index.js +2 -2
  8. package/dist/harness/core/agent-pack.d.ts +26 -6
  9. package/dist/harness/core/sdd.d.ts +27 -15
  10. package/dist/harness/writers/codex-plugin-package.d.ts +1 -0
  11. package/dist/index.js +2 -2
  12. package/package.json +4 -4
  13. package/plugin/.claude-plugin/.thoth-agents-plugin-assets.json +2 -2
  14. package/plugin/.claude-plugin/plugin.json +1 -1
  15. package/plugin/.codex-plugin/plugin.json +1 -1
  16. package/plugin/agents/oracle.md +3 -3
  17. package/plugin/agents/orchestrator.md +35 -30
  18. package/plugin/skills/plan-reviewer/SKILL.md +54 -15
  19. package/plugin/skills/thoth-archive/SKILL.md +5 -0
  20. package/plugin/skills/thoth-constitution/SKILL.md +5 -0
  21. package/plugin/skills/thoth-constitution/templates/constitution.md +19 -2
  22. package/plugin/skills/thoth-init/SKILL.md +5 -0
  23. package/plugin/skills/thoth-sdd/SKILL.md +35 -10
  24. package/plugin/skills/thoth-sdd/references/phases/implement.md +13 -2
  25. package/plugin/skills/thoth-sdd/references/phases/tasks.md +23 -2
  26. package/plugin/skills/thoth-sdd/references/phases/verify.md +7 -5
  27. package/plugin/skills/thoth-sdd/scripts/validate.mjs +263 -30
  28. package/plugin/skills/thoth-sdd/templates/tasks.md +18 -2
  29. package/skills/README.md +2 -1
  30. package/skills/plan-reviewer/SKILL.md +54 -15
  31. package/skills/thoth-archive/SKILL.md +5 -0
  32. package/skills/thoth-constitution/SKILL.md +5 -0
  33. package/skills/thoth-constitution/templates/constitution.md +19 -2
  34. package/skills/thoth-init/SKILL.md +5 -0
  35. package/skills/thoth-sdd/SKILL.md +35 -10
  36. package/skills/thoth-sdd/references/phases/implement.md +13 -2
  37. package/skills/thoth-sdd/references/phases/tasks.md +23 -2
  38. package/skills/thoth-sdd/references/phases/verify.md +7 -5
  39. package/skills/thoth-sdd/scripts/validate.mjs +263 -30
  40. package/skills/thoth-sdd/templates/tasks.md +18 -2
  41. package/.agents/plugins/marketplace.json +0 -20
  42. package/.claude-plugin/marketplace.json +0 -21
@@ -540,6 +540,97 @@ function exactTaskPath(line) {
540
540
  return normalized.toLowerCase();
541
541
  }
542
542
 
543
+ function taskDependencyEdges(content) {
544
+ return [...content.matchAll(/\bT\d{3}(?:\s*->\s*T\d{3})+\b/g)].flatMap(
545
+ ([chain]) => {
546
+ const ids = chain.match(/\bT\d{3}\b/g) ?? [];
547
+ return ids.slice(1).map((target, index) => ({
548
+ source: ids[index],
549
+ target,
550
+ }));
551
+ },
552
+ );
553
+ }
554
+
555
+ function parseParallelGroups(content) {
556
+ const lines = content.split(/\r?\n/);
557
+ const headingIndexes = lines
558
+ .map((line, index) => (/^###\s+Group\s+P\d+\s*$/.test(line) ? index : -1))
559
+ .filter((index) => index >= 0);
560
+ const groups = [];
561
+ let valid = headingIndexes.length > 0;
562
+
563
+ if (
564
+ lines
565
+ .slice(0, headingIndexes[0] ?? lines.length)
566
+ .some((line) => line.trim())
567
+ ) {
568
+ valid = false;
569
+ }
570
+
571
+ for (const [groupIndex, start] of headingIndexes.entries()) {
572
+ const end = headingIndexes[groupIndex + 1] ?? lines.length;
573
+ const groupId = /^###\s+Group\s+P(\d+)\s*$/.exec(lines[start])?.[1];
574
+ const body = lines.slice(start + 1, end).filter((line) => line.trim());
575
+ const lanes = [];
576
+ let prerequisites;
577
+ let barrier;
578
+ let rationale;
579
+ for (const line of body) {
580
+ const lane =
581
+ /^- Lane L(\d+): (T\d{3}(?: -> T\d{3})*)(?: \| Owner: (\S+))?\s*$/.exec(
582
+ line,
583
+ );
584
+ if (lane) {
585
+ lanes.push({
586
+ number: Number.parseInt(lane[1], 10),
587
+ taskIds: lane[2].split(' -> '),
588
+ owner: lane[3],
589
+ });
590
+ continue;
591
+ }
592
+ const prerequisiteLine =
593
+ /^- Prerequisites: (None|T\d{3}(?:, T\d{3})*)\s*$/.exec(line);
594
+ if (prerequisiteLine && prerequisites === undefined) {
595
+ prerequisites =
596
+ prerequisiteLine[1] === 'None' ? [] : prerequisiteLine[1].split(', ');
597
+ continue;
598
+ }
599
+ const barrierLine = /^- Barrier: (Final verification|T\d{3})\s*$/.exec(
600
+ line,
601
+ );
602
+ if (barrierLine && barrier === undefined) {
603
+ barrier = barrierLine[1];
604
+ continue;
605
+ }
606
+ const rationaleLine = /^- Rationale: (\S.*)$/.exec(line);
607
+ if (rationaleLine && rationale === undefined) {
608
+ rationale = rationaleLine[1];
609
+ continue;
610
+ }
611
+ valid = false;
612
+ }
613
+ if (
614
+ groupId === undefined ||
615
+ lanes.length < 2 ||
616
+ rationale === undefined ||
617
+ !isConcreteEvidence(rationale) ||
618
+ lanes.some((lane, index) => lane.number !== index + 1)
619
+ ) {
620
+ valid = false;
621
+ }
622
+ groups.push({
623
+ number: Number.parseInt(groupId ?? '0', 10),
624
+ lanes,
625
+ prerequisites,
626
+ barrier,
627
+ rationale,
628
+ });
629
+ }
630
+ if (groups.some((group, index) => group.number !== index + 1)) valid = false;
631
+ return { groups, valid };
632
+ }
633
+
543
634
  function validateTasks(content, errors, spec) {
544
635
  const artifact = 'tasks.md';
545
636
  const taskLines = content
@@ -644,12 +735,6 @@ function validateTasks(content, errors, spec) {
644
735
  }));
645
736
  const tasksById = new Map(taskRecords.map((task) => [task.id, task]));
646
737
  const hasParallelTask = taskRecords.some((task) => task.parallel);
647
- const parallelLines = (parallelSection ?? '')
648
- .split(/\r?\n/)
649
- .map((line) => [line, [...new Set(line.match(/\bT\d{3}\b/g) ?? [])]])
650
- .filter(([, ids]) => ids.length >= 2);
651
- const referencedTaskIds = new Set(parallelLines.flatMap(([, ids]) => ids));
652
- const hasParallelExample = parallelLines.length > 0;
653
738
  const noParallelMatch = /^-\s+None:\s*(\S.*)$/im.exec(parallelSection ?? '');
654
739
  const explainsNoParallelWork =
655
740
  noParallelMatch !== null && isConcreteEvidence(noParallelMatch[1]);
@@ -657,42 +742,190 @@ function validateTasks(content, errors, spec) {
657
742
  left === right ||
658
743
  left.startsWith(`${right}/`) ||
659
744
  right.startsWith(`${left}/`);
660
- const unknownReferences = [...referencedTaskIds].filter(
661
- (id) => !tasksById.has(id),
745
+ const noneOnly =
746
+ explainsNoParallelWork &&
747
+ parallelSection?.trim() === noParallelMatch?.[0].trim();
748
+ const { groups, valid: validGroupGrammar } = parseParallelGroups(
749
+ parallelSection ?? '',
662
750
  );
663
- const omittedParallelTasks = taskRecords
664
- .filter((task) => task.parallel && !referencedTaskIds.has(task.id))
665
- .map((task) => task.id);
666
- const examplesWithoutParallelTask = parallelLines.some(
667
- ([, ids]) => !ids.some((id) => tasksById.get(id)?.parallel),
751
+ const declaredDependencies = taskDependencyEdges(
752
+ sectionContent(content, 'Dependencies') ?? '',
668
753
  );
669
- const overlappingExamples = parallelLines.some(([, ids]) => {
670
- const paths = ids.map((id) => tasksById.get(id)?.path);
671
- if (paths.some((path) => path === undefined)) return true;
672
- return paths.some((path, index) =>
673
- paths.slice(index + 1).some((other) => pathsOverlap(path, other)),
674
- );
675
- });
754
+ const hasGroups = groups.length > 0;
676
755
  if (
677
756
  parallelSection === undefined ||
678
- (explainsNoParallelWork && hasParallelExample) ||
679
- (hasParallelTask &&
680
- (!hasParallelExample ||
681
- explainsNoParallelWork ||
682
- unknownReferences.length > 0 ||
683
- omittedParallelTasks.length > 0 ||
684
- examplesWithoutParallelTask ||
685
- overlappingExamples)) ||
686
- (!hasParallelTask && !explainsNoParallelWork)
757
+ (hasParallelTask && (!hasGroups || explainsNoParallelWork)) ||
758
+ (!hasParallelTask && !noneOnly) ||
759
+ (explainsNoParallelWork && !noneOnly)
687
760
  ) {
688
761
  errors.push(
689
762
  issue(
690
763
  'SDD-TASK-PARALLEL',
691
764
  artifact,
692
- 'Document existing [P] tasks with non-overlapping paths, or explain why no safe parallel work exists.',
765
+ 'Declare strict parallel groups for [P] tasks, or provide one exclusive evidence-backed None reason.',
766
+ ),
767
+ );
768
+ }
769
+ if (hasGroups && !validGroupGrammar) {
770
+ errors.push(
771
+ issue(
772
+ 'SDD-TASK-LANE-GRAMMAR',
773
+ artifact,
774
+ 'Groups and lanes must use unique sequential IDs, at least two lanes, and every required field.',
693
775
  ),
694
776
  );
695
777
  }
778
+
779
+ if (hasGroups && validGroupGrammar) {
780
+ const memberships = groups.flatMap((group) =>
781
+ group.lanes.flatMap((lane) =>
782
+ lane.taskIds.map((taskId) => ({ group, lane, taskId })),
783
+ ),
784
+ );
785
+ const membershipCounts = new Map();
786
+ for (const { taskId } of memberships) {
787
+ membershipCounts.set(taskId, (membershipCounts.get(taskId) ?? 0) + 1);
788
+ }
789
+ const invalidMembership =
790
+ memberships.some(({ taskId }) => !tasksById.get(taskId)?.parallel) ||
791
+ taskRecords.some(
792
+ (task) => task.parallel && membershipCounts.get(task.id) !== 1,
793
+ );
794
+ if (invalidMembership) {
795
+ errors.push(
796
+ issue(
797
+ 'SDD-TASK-LANE-MEMBERSHIP',
798
+ artifact,
799
+ 'Every [P] task must belong to exactly one lane, and lane members must be known [P] tasks.',
800
+ ),
801
+ );
802
+ }
803
+
804
+ if (
805
+ groups.some((group) =>
806
+ group.lanes.some(
807
+ (lane) => !['designer', 'quick', 'deep'].includes(lane.owner),
808
+ ),
809
+ )
810
+ ) {
811
+ errors.push(
812
+ issue(
813
+ 'SDD-TASK-LANE-OWNER',
814
+ artifact,
815
+ 'Every lane owner must be designer, quick, or deep.',
816
+ ),
817
+ );
818
+ }
819
+
820
+ for (const group of groups) {
821
+ const rationale = group.rationale ?? '';
822
+ const hasPathIndependenceEvidence =
823
+ /\b(?:path|file|surface)s?\b/i.test(rationale) &&
824
+ /\b(?:disjoint|non[- ]?overlap(?:ping)?)\b/i.test(rationale);
825
+ const hasDependencyIndependenceEvidence =
826
+ /\b(?:dependenc(?:y|ies)|consum(?:e|es|ing)|peer output|data flow)\b/i.test(
827
+ rationale,
828
+ );
829
+ if (!hasPathIndependenceEvidence || !hasDependencyIndependenceEvidence) {
830
+ errors.push(
831
+ issue(
832
+ 'SDD-TASK-LANE-RATIONALE',
833
+ artifact,
834
+ 'Parallel rationale must state concrete path-disjointness and cross-lane dependency evidence for Oracle review.',
835
+ ),
836
+ );
837
+ }
838
+
839
+ const memberIds = new Set(group.lanes.flatMap((lane) => lane.taskIds));
840
+ const prerequisites = group.prerequisites ?? [];
841
+ if (
842
+ group.prerequisites === undefined ||
843
+ new Set(prerequisites).size !== prerequisites.length ||
844
+ prerequisites.some(
845
+ (taskId) => !tasksById.has(taskId) || memberIds.has(taskId),
846
+ )
847
+ ) {
848
+ errors.push(
849
+ issue(
850
+ 'SDD-TASK-LANE-PREREQUISITE',
851
+ artifact,
852
+ 'Prerequisites must be unique known tasks outside the group.',
853
+ ),
854
+ );
855
+ }
856
+
857
+ const memberNumbers = [...memberIds].map((id) =>
858
+ Number.parseInt(id.slice(1), 10),
859
+ );
860
+ const barrierNumber = /^T\d{3}$/.test(group.barrier ?? '')
861
+ ? Number.parseInt(group.barrier.slice(1), 10)
862
+ : undefined;
863
+ if (
864
+ group.barrier !== 'Final verification' &&
865
+ (barrierNumber === undefined ||
866
+ !tasksById.has(group.barrier) ||
867
+ memberIds.has(group.barrier) ||
868
+ prerequisites.includes(group.barrier) ||
869
+ memberNumbers.some((number) => barrierNumber <= number))
870
+ ) {
871
+ errors.push(
872
+ issue(
873
+ 'SDD-TASK-LANE-BARRIER',
874
+ artifact,
875
+ 'A task barrier must be a known downstream non-member task, or use exact Final verification.',
876
+ ),
877
+ );
878
+ }
879
+
880
+ const laneByTask = new Map(
881
+ group.lanes.flatMap((lane) =>
882
+ lane.taskIds.map((taskId) => [taskId, lane.number]),
883
+ ),
884
+ );
885
+ if (
886
+ declaredDependencies.some(
887
+ ({ source, target }) =>
888
+ laneByTask.has(source) &&
889
+ laneByTask.has(target) &&
890
+ laneByTask.get(source) !== laneByTask.get(target),
891
+ )
892
+ ) {
893
+ errors.push(
894
+ issue(
895
+ 'SDD-TASK-LANE-DEPENDENCY',
896
+ artifact,
897
+ 'Declared dependencies cannot connect different lanes in one group.',
898
+ ),
899
+ );
900
+ }
901
+
902
+ const lanePaths = group.lanes.map((lane) =>
903
+ lane.taskIds.map((taskId) => tasksById.get(taskId)?.path),
904
+ );
905
+ const overlaps = lanePaths.some((paths, index) =>
906
+ lanePaths
907
+ .slice(index + 1)
908
+ .some((otherPaths) =>
909
+ paths.some(
910
+ (path) =>
911
+ path !== undefined &&
912
+ otherPaths.some(
913
+ (other) => other !== undefined && pathsOverlap(path, other),
914
+ ),
915
+ ),
916
+ ),
917
+ );
918
+ if (overlaps) {
919
+ errors.push(
920
+ issue(
921
+ 'SDD-TASK-LANE-OVERLAP',
922
+ artifact,
923
+ 'Mutable task paths cannot overlap across lanes in one group.',
924
+ ),
925
+ );
926
+ }
927
+ }
928
+ }
696
929
  }
697
930
 
698
931
  function validateChecklist(content, errors, spec, requireComplete) {
@@ -32,8 +32,24 @@ for them.
32
32
 
33
33
  ## Parallel execution
34
34
 
35
- [Replace the anchor with exactly one form: concrete task pairings covering every
36
- `[P]` task, or `- None: <evidence-backed reason>`. Never keep both forms.]
35
+ Use this exact grammar when safe parallel work exists:
36
+
37
+ ### Group P1
38
+
39
+ - Lane L1: T001 -> T002 | Owner: deep
40
+ - Lane L2: T003 -> T004 | Owner: quick
41
+ - Prerequisites: None
42
+ - Barrier: Final verification
43
+ - Rationale: Both lane path sets are disjoint and neither lane consumes peer output.
44
+
45
+ Groups and lanes are sequential and unique. Every `[P]` task belongs to exactly one lane; non-`[P]` tasks do not. Lane surfaces are the union of exact task
46
+ paths, and lanes in a group have disjoint surfaces and no cross-lane dependency.
47
+ The `Rationale` must explicitly state path-disjointness and cross-lane dependency
48
+ evidence so structural validation can gate the evidence shape and Oracle can
49
+ judge whether the claim is true.
50
+ Prerequisites name known tasks outside the group; the barrier is a downstream
51
+ task or `Final verification` after all lane members. If no group exists, use
52
+ only `- None: <evidence-backed reason>` and no task may use `[P]`.
37
53
 
38
54
  <!-- PARALLEL-EXECUTION-EVIDENCE -->
39
55
 
@@ -1,20 +0,0 @@
1
- {
2
- "name": "thoth-agents",
3
- "interface": {
4
- "displayName": "thoth-agents Adaptive Orchestration"
5
- },
6
- "plugins": [
7
- {
8
- "name": "thoth-agents",
9
- "source": {
10
- "source": "local",
11
- "path": "./plugin"
12
- },
13
- "policy": {
14
- "installation": "AVAILABLE",
15
- "authentication": "ON_INSTALL"
16
- },
17
- "category": "Productivity"
18
- }
19
- ]
20
- }
@@ -1,21 +0,0 @@
1
- {
2
- "$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
3
- "name": "thoth-agents",
4
- "description": "Adaptive orchestration with seven roles and runtime-autonomous direct, accelerated, and full Spec Kit-compatible SDD routes.",
5
- "owner": {
6
- "name": "thoth-agents maintainers"
7
- },
8
- "plugins": [
9
- {
10
- "name": "thoth-agents",
11
- "description": "Adaptive root orchestration, six specialist subagents, and a runtime-autonomous Spec Kit-compatible SDD bundle.",
12
- "version": "0.3.11",
13
- "author": {
14
- "name": "thoth-agents maintainers"
15
- },
16
- "source": "./plugin",
17
- "category": "productivity",
18
- "homepage": "https://github.com/EremesNG/thoth-agents"
19
- }
20
- ]
21
- }