agentweaver 0.1.14 → 0.1.15

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.
@@ -0,0 +1,112 @@
1
+ import { existsSync } from "node:fs";
2
+ import { designFile, designJsonFile, jiraAttachmentsContextFile, jiraAttachmentsManifestFile, jiraTaskFile, latestArtifactIteration, planFile, planJsonFile, planningAnswersJsonFile, qaFile, qaJsonFile, requireArtifacts, } from "../artifacts.js";
3
+ import { TaskRunnerError } from "../errors.js";
4
+ import { validateStructuredArtifacts } from "../structured-artifacts.js";
5
+ const OPTIONAL_INPUT_NOT_PROVIDED = "not provided";
6
+ function requiredPlanningArtifactPaths(taskKey, iteration) {
7
+ return {
8
+ designFile: designFile(taskKey, iteration),
9
+ designJsonFile: designJsonFile(taskKey, iteration),
10
+ planFile: planFile(taskKey, iteration),
11
+ planJsonFile: planJsonFile(taskKey, iteration),
12
+ };
13
+ }
14
+ function resolveLatestCompletedPlanningIteration(taskKey) {
15
+ const latestKnownIteration = Math.max(latestArtifactIteration(taskKey, "design", "md") ?? 0, latestArtifactIteration(taskKey, "design", "json") ?? 0, latestArtifactIteration(taskKey, "plan", "md") ?? 0, latestArtifactIteration(taskKey, "plan", "json") ?? 0);
16
+ for (let iteration = latestKnownIteration; iteration >= 1; iteration -= 1) {
17
+ const requiredPaths = Object.values(requiredPlanningArtifactPaths(taskKey, iteration));
18
+ if (requiredPaths.every((candidate) => existsSync(candidate))) {
19
+ return iteration;
20
+ }
21
+ }
22
+ const fallbackIteration = latestKnownIteration || 1;
23
+ const fallbackPaths = Object.values(requiredPlanningArtifactPaths(taskKey, fallbackIteration));
24
+ requireArtifacts(fallbackPaths, "Design-review requires design and plan markdown/JSON artifacts from the latest completed planning run.");
25
+ throw new TaskRunnerError("Unreachable design-review planning artifact resolution state.");
26
+ }
27
+ function resolveOptionalPromptFile(filePath) {
28
+ if (!existsSync(filePath)) {
29
+ return {
30
+ present: false,
31
+ path: null,
32
+ promptValue: OPTIONAL_INPUT_NOT_PROVIDED,
33
+ };
34
+ }
35
+ return {
36
+ present: true,
37
+ path: filePath,
38
+ promptValue: filePath,
39
+ };
40
+ }
41
+ function resolveOptionalValidatedStructuredFile(filePath, schemaId, message) {
42
+ const resolved = resolveOptionalPromptFile(filePath);
43
+ if (!resolved.present) {
44
+ return resolved;
45
+ }
46
+ validateStructuredArtifacts([{ path: filePath, schemaId }], message);
47
+ return resolved;
48
+ }
49
+ function resolveOptionalQaPair(taskKey, iteration) {
50
+ const markdownPath = qaFile(taskKey, iteration);
51
+ const jsonPath = qaJsonFile(taskKey, iteration);
52
+ const markdownExists = existsSync(markdownPath);
53
+ const jsonExists = existsSync(jsonPath);
54
+ if (!markdownExists && !jsonExists) {
55
+ return {
56
+ hasQaArtifacts: false,
57
+ qaFilePath: null,
58
+ qaJsonFilePath: null,
59
+ qaFile: OPTIONAL_INPUT_NOT_PROVIDED,
60
+ qaJsonFile: OPTIONAL_INPUT_NOT_PROVIDED,
61
+ };
62
+ }
63
+ if (!markdownExists || !jsonExists) {
64
+ requireArtifacts([markdownPath, jsonPath], "Design-review accepts QA artifacts only as a complete markdown/JSON pair for the selected planning iteration.");
65
+ }
66
+ validateStructuredArtifacts([{ path: jsonPath, schemaId: "qa-plan/v1" }], "Design-review QA structured artifact is invalid.");
67
+ return {
68
+ hasQaArtifacts: true,
69
+ qaFilePath: markdownPath,
70
+ qaJsonFilePath: jsonPath,
71
+ qaFile: markdownPath,
72
+ qaJsonFile: jsonPath,
73
+ };
74
+ }
75
+ /**
76
+ * Resolves the full design-review input contract from artifacts already present in the task scope.
77
+ * Required planning artifacts must come from one completed planning iteration. Optional contextual
78
+ * inputs are exposed as stable prompt values and explicit presence flags so the flow can remain
79
+ * deterministic when some context is absent.
80
+ */
81
+ export function resolveDesignReviewInputContract(taskKey) {
82
+ const planningIteration = resolveLatestCompletedPlanningIteration(taskKey);
83
+ const requiredArtifacts = requiredPlanningArtifactPaths(taskKey, planningIteration);
84
+ requireArtifacts(Object.values(requiredArtifacts), "Design-review requires design and plan markdown/JSON artifacts from the latest completed planning run.");
85
+ validateStructuredArtifacts([
86
+ { path: requiredArtifacts.designJsonFile, schemaId: "implementation-design/v1" },
87
+ { path: requiredArtifacts.planJsonFile, schemaId: "implementation-plan/v1" },
88
+ ], "Design-review required planning structured artifacts are invalid.");
89
+ const qaArtifacts = resolveOptionalQaPair(taskKey, planningIteration);
90
+ const jiraTask = resolveOptionalPromptFile(jiraTaskFile(taskKey));
91
+ const jiraAttachmentsManifest = resolveOptionalPromptFile(jiraAttachmentsManifestFile(taskKey));
92
+ const jiraAttachmentsContext = resolveOptionalPromptFile(jiraAttachmentsContextFile(taskKey));
93
+ const planningAnswers = resolveOptionalValidatedStructuredFile(planningAnswersJsonFile(taskKey), "user-input/v1", "Design-review planning answers structured artifact is invalid.");
94
+ return {
95
+ planningIteration,
96
+ ...requiredArtifacts,
97
+ ...qaArtifacts,
98
+ hasJiraTaskFile: jiraTask.present,
99
+ jiraTaskFilePath: jiraTask.path,
100
+ jiraTaskFile: jiraTask.promptValue,
101
+ hasJiraAttachmentsManifestFile: jiraAttachmentsManifest.present,
102
+ jiraAttachmentsManifestFilePath: jiraAttachmentsManifest.path,
103
+ jiraAttachmentsManifestFile: jiraAttachmentsManifest.promptValue,
104
+ hasJiraAttachmentsContextFile: jiraAttachmentsContext.present,
105
+ jiraAttachmentsContextFilePath: jiraAttachmentsContext.path,
106
+ jiraAttachmentsContextFile: jiraAttachmentsContext.promptValue,
107
+ hasPlanningAnswersJsonFile: planningAnswers.present,
108
+ planningAnswersJsonFilePath: planningAnswers.path,
109
+ planningAnswersJsonFile: planningAnswers.promptValue,
110
+ };
111
+ }
112
+ export { OPTIONAL_INPUT_NOT_PROVIDED };
@@ -0,0 +1,144 @@
1
+ import { existsSync } from "node:fs";
2
+ import { designFile, designJsonFile, designReviewFile, designReviewJsonFile, jiraAttachmentsContextFile, jiraAttachmentsManifestFile, jiraTaskFile, latestArtifactIteration, planFile, planJsonFile, planningAnswersJsonFile, qaFile, qaJsonFile, requireArtifacts, } from "../artifacts.js";
3
+ import { TaskRunnerError } from "../errors.js";
4
+ import { validateStructuredArtifacts } from "../structured-artifacts.js";
5
+ const OPTIONAL_INPUT_NOT_PROVIDED = "not provided";
6
+ function resolveLatestDesignReviewIteration(taskKey) {
7
+ const latestMd = latestArtifactIteration(taskKey, "design-review", "md");
8
+ const latestJson = latestArtifactIteration(taskKey, "design-review", "json");
9
+ const maxIteration = Math.max(latestMd ?? 0, latestJson ?? 0);
10
+ if (maxIteration === 0) {
11
+ throw new TaskRunnerError("Plan-revise requires at least one completed design-review iteration, but no design-review artifacts were found.");
12
+ }
13
+ for (let iteration = maxIteration; iteration >= 1; iteration -= 1) {
14
+ const mdPath = designReviewFile(taskKey, iteration);
15
+ const jsonPath = designReviewJsonFile(taskKey, iteration);
16
+ if (existsSync(mdPath) && existsSync(jsonPath)) {
17
+ return iteration;
18
+ }
19
+ }
20
+ const fallbackMd = designReviewFile(taskKey, maxIteration);
21
+ const fallbackJson = designReviewJsonFile(taskKey, maxIteration);
22
+ requireArtifacts([fallbackMd, fallbackJson], "Plan-revise requires design-review markdown and JSON artifacts from the latest completed design-review run.");
23
+ throw new TaskRunnerError("Unreachable plan-revise design-review artifact resolution state.");
24
+ }
25
+ function resolveLatestCompletedPlanningIteration(taskKey) {
26
+ const latestDesignMd = latestArtifactIteration(taskKey, "design", "md") ?? 0;
27
+ const latestDesignJson = latestArtifactIteration(taskKey, "design", "json") ?? 0;
28
+ const latestPlanMd = latestArtifactIteration(taskKey, "plan", "md") ?? 0;
29
+ const latestPlanJson = latestArtifactIteration(taskKey, "plan", "json") ?? 0;
30
+ const maxIteration = Math.max(latestDesignMd, latestDesignJson, latestPlanMd, latestPlanJson);
31
+ for (let iteration = maxIteration; iteration >= 1; iteration -= 1) {
32
+ const paths = [
33
+ designFile(taskKey, iteration),
34
+ designJsonFile(taskKey, iteration),
35
+ planFile(taskKey, iteration),
36
+ planJsonFile(taskKey, iteration),
37
+ ];
38
+ if (paths.every((candidate) => existsSync(candidate))) {
39
+ return iteration;
40
+ }
41
+ }
42
+ const fallbackIteration = maxIteration || 1;
43
+ const fallbackPaths = [
44
+ designFile(taskKey, fallbackIteration),
45
+ designJsonFile(taskKey, fallbackIteration),
46
+ planFile(taskKey, fallbackIteration),
47
+ planJsonFile(taskKey, fallbackIteration),
48
+ ];
49
+ requireArtifacts(fallbackPaths, "Plan-revise requires design and plan markdown/JSON artifacts from the latest completed planning run.");
50
+ throw new TaskRunnerError("Unreachable plan-revise planning artifact resolution state.");
51
+ }
52
+ function resolveOptionalPromptFile(filePath) {
53
+ if (!existsSync(filePath)) {
54
+ return {
55
+ present: false,
56
+ path: null,
57
+ promptValue: OPTIONAL_INPUT_NOT_PROVIDED,
58
+ };
59
+ }
60
+ return {
61
+ present: true,
62
+ path: filePath,
63
+ promptValue: filePath,
64
+ };
65
+ }
66
+ function resolveOptionalQaPair(taskKey, iteration) {
67
+ const markdownPath = qaFile(taskKey, iteration);
68
+ const jsonPath = qaJsonFile(taskKey, iteration);
69
+ const markdownExists = existsSync(markdownPath);
70
+ const jsonExists = existsSync(jsonPath);
71
+ if (!markdownExists && !jsonExists) {
72
+ return {
73
+ hasQaArtifacts: false,
74
+ qaFilePath: null,
75
+ qaJsonFilePath: null,
76
+ qaFile: OPTIONAL_INPUT_NOT_PROVIDED,
77
+ qaJsonFile: OPTIONAL_INPUT_NOT_PROVIDED,
78
+ };
79
+ }
80
+ if (!markdownExists || !jsonExists) {
81
+ requireArtifacts([markdownPath, jsonPath], "Plan-revise accepts QA artifacts only as a complete markdown/JSON pair for the source planning iteration.");
82
+ }
83
+ validateStructuredArtifacts([{ path: jsonPath, schemaId: "qa-plan/v1" }], "Plan-revise QA structured artifact is invalid.");
84
+ return {
85
+ hasQaArtifacts: true,
86
+ qaFilePath: markdownPath,
87
+ qaJsonFilePath: jsonPath,
88
+ qaFile: markdownPath,
89
+ qaJsonFile: jsonPath,
90
+ };
91
+ }
92
+ export function resolvePlanReviseInputContract(taskKey) {
93
+ const reviewIteration = resolveLatestDesignReviewIteration(taskKey);
94
+ const reviewMd = designReviewFile(taskKey, reviewIteration);
95
+ const reviewJson = designReviewJsonFile(taskKey, reviewIteration);
96
+ requireArtifacts([reviewMd, reviewJson], "Plan-revise requires design-review markdown and JSON artifacts.");
97
+ validateStructuredArtifacts([{ path: reviewJson, schemaId: "design-review/v1" }], "Plan-revise design-review structured artifact is invalid.");
98
+ const sourcePlanningIteration = resolveLatestCompletedPlanningIteration(taskKey);
99
+ const srcDesignMd = designFile(taskKey, sourcePlanningIteration);
100
+ const srcDesignJson = designJsonFile(taskKey, sourcePlanningIteration);
101
+ const srcPlanMd = planFile(taskKey, sourcePlanningIteration);
102
+ const srcPlanJson = planJsonFile(taskKey, sourcePlanningIteration);
103
+ requireArtifacts([srcDesignMd, srcDesignJson, srcPlanMd, srcPlanJson], "Plan-revise requires design and plan markdown/JSON artifacts from the source planning iteration.");
104
+ validateStructuredArtifacts([
105
+ { path: srcDesignJson, schemaId: "implementation-design/v1" },
106
+ { path: srcPlanJson, schemaId: "implementation-plan/v1" },
107
+ ], "Plan-revise source planning structured artifacts are invalid.");
108
+ const outputIteration = sourcePlanningIteration + 1;
109
+ const qaArtifacts = resolveOptionalQaPair(taskKey, sourcePlanningIteration);
110
+ const jiraTask = resolveOptionalPromptFile(jiraTaskFile(taskKey));
111
+ const jiraAttachmentsManifest = resolveOptionalPromptFile(jiraAttachmentsManifestFile(taskKey));
112
+ const jiraAttachmentsContext = resolveOptionalPromptFile(jiraAttachmentsContextFile(taskKey));
113
+ const planningAnswers = resolveOptionalPromptFile(planningAnswersJsonFile(taskKey));
114
+ return {
115
+ reviewIteration,
116
+ reviewFile: reviewMd,
117
+ reviewJsonFile: reviewJson,
118
+ sourcePlanningIteration,
119
+ outputIteration,
120
+ designFile: srcDesignMd,
121
+ designJsonFile: srcDesignJson,
122
+ planFile: srcPlanMd,
123
+ planJsonFile: srcPlanJson,
124
+ ...qaArtifacts,
125
+ revisedDesignFile: designFile(taskKey, outputIteration),
126
+ revisedDesignJsonFile: designJsonFile(taskKey, outputIteration),
127
+ revisedPlanFile: planFile(taskKey, outputIteration),
128
+ revisedPlanJsonFile: planJsonFile(taskKey, outputIteration),
129
+ revisedQaFile: qaFile(taskKey, outputIteration),
130
+ revisedQaJsonFile: qaJsonFile(taskKey, outputIteration),
131
+ hasJiraTaskFile: jiraTask.present,
132
+ jiraTaskFilePath: jiraTask.path,
133
+ jiraTaskFile: jiraTask.promptValue,
134
+ hasJiraAttachmentsManifestFile: jiraAttachmentsManifest.present,
135
+ jiraAttachmentsManifestFilePath: jiraAttachmentsManifest.path,
136
+ jiraAttachmentsManifestFile: jiraAttachmentsManifest.promptValue,
137
+ hasJiraAttachmentsContextFile: jiraAttachmentsContext.present,
138
+ jiraAttachmentsContextFilePath: jiraAttachmentsContext.path,
139
+ jiraAttachmentsContextFile: jiraAttachmentsContext.promptValue,
140
+ hasPlanningAnswersJsonFile: planningAnswers.present,
141
+ planningAnswersJsonFilePath: planningAnswers.path,
142
+ planningAnswersJsonFile: planningAnswers.promptValue,
143
+ };
144
+ }
@@ -0,0 +1,10 @@
1
+ import { existsSync, rmSync } from "node:fs";
2
+ import { readyToMergeFile } from "../artifacts.js";
3
+ export function clearReadyToMergeFile(taskKey) {
4
+ const filePath = readyToMergeFile(taskKey);
5
+ if (!existsSync(filePath)) {
6
+ return false;
7
+ }
8
+ rmSync(filePath);
9
+ return true;
10
+ }
package/dist/scope.js CHANGED
@@ -43,7 +43,7 @@ export function detectGitBranchName() {
43
43
  export function detectProjectRoot() {
44
44
  return gitOutput(["rev-parse", "--show-toplevel"]) ?? process.cwd();
45
45
  }
46
- export function buildProjectScopeKey(explicitScope) {
46
+ export function buildProjectScopeKey(explicitScope, jiraIssueKey) {
47
47
  const projectRoot = detectProjectRoot();
48
48
  const worktreeHash = shortHash(projectRoot);
49
49
  if (explicitScope?.trim()) {
@@ -54,6 +54,14 @@ export function buildProjectScopeKey(explicitScope) {
54
54
  projectRoot,
55
55
  };
56
56
  }
57
+ if (jiraIssueKey?.trim()) {
58
+ return {
59
+ scopeKey: `${sanitizeScopeName(jiraIssueKey)}@${worktreeHash}`,
60
+ gitBranchName: detectGitBranchName(),
61
+ worktreeHash,
62
+ projectRoot,
63
+ };
64
+ }
57
65
  const branchName = detectGitBranchName();
58
66
  const branchSlug = sanitizeScopeName(branchName ?? "detached-head");
59
67
  return {
@@ -72,7 +80,8 @@ export function parseJiraContext(jiraRef) {
72
80
  };
73
81
  }
74
82
  export function resolveProjectScope(explicitScope, jiraRef) {
75
- const { scopeKey, gitBranchName, worktreeHash, projectRoot } = buildProjectScopeKey(explicitScope);
83
+ const jiraIssueKey = jiraRef?.trim() ? extractIssueKey(jiraRef) : undefined;
84
+ const { scopeKey, gitBranchName, worktreeHash, projectRoot } = buildProjectScopeKey(explicitScope, jiraIssueKey);
76
85
  ensureScopeWorkspaceDir(scopeKey);
77
86
  const baseScope = {
78
87
  scopeType: "project",
@@ -6,6 +6,7 @@ export const STRUCTURED_ARTIFACT_SCHEMA_IDS = [
6
6
  "bug-analysis/v1",
7
7
  "bug-fix-design/v1",
8
8
  "bug-fix-plan/v1",
9
+ "design-review/v1",
9
10
  "gitlab-mr-diff/v1",
10
11
  "gitlab-review/v1",
11
12
  "implementation-design/v1",
@@ -485,6 +485,123 @@
485
485
  },
486
486
  "required": ["summary", "test_scenarios", "non_functional_checks"]
487
487
  },
488
+ "design-review/v1": {
489
+ "type": "object",
490
+ "properties": {
491
+ "summary": { "type": "string", "nonEmpty": true },
492
+ "status": {
493
+ "type": "string",
494
+ "enum": ["approved", "approved_with_warnings", "needs_revision"]
495
+ },
496
+ "blocking_findings": {
497
+ "type": "array",
498
+ "items": {
499
+ "type": "object",
500
+ "properties": {
501
+ "title": { "type": "string", "nonEmpty": true },
502
+ "description": { "type": "string", "nonEmpty": true },
503
+ "affected_artifacts": {
504
+ "type": "array",
505
+ "items": { "type": "string", "nonEmpty": true },
506
+ "minItems": 1
507
+ }
508
+ },
509
+ "required": ["title", "description", "affected_artifacts"]
510
+ }
511
+ },
512
+ "major_findings": {
513
+ "type": "array",
514
+ "items": {
515
+ "type": "object",
516
+ "properties": {
517
+ "title": { "type": "string", "nonEmpty": true },
518
+ "description": { "type": "string", "nonEmpty": true },
519
+ "affected_artifacts": {
520
+ "type": "array",
521
+ "items": { "type": "string", "nonEmpty": true },
522
+ "minItems": 1
523
+ }
524
+ },
525
+ "required": ["title", "description", "affected_artifacts"]
526
+ }
527
+ },
528
+ "warnings": {
529
+ "type": "array",
530
+ "items": {
531
+ "type": "object",
532
+ "properties": {
533
+ "title": { "type": "string", "nonEmpty": true },
534
+ "description": { "type": "string", "nonEmpty": true },
535
+ "affected_artifacts": {
536
+ "type": "array",
537
+ "items": { "type": "string", "nonEmpty": true },
538
+ "minItems": 1
539
+ }
540
+ },
541
+ "required": ["title", "description", "affected_artifacts"]
542
+ }
543
+ },
544
+ "missing_information": {
545
+ "type": "array",
546
+ "items": {
547
+ "type": "object",
548
+ "properties": {
549
+ "title": { "type": "string", "nonEmpty": true },
550
+ "description": { "type": "string", "nonEmpty": true },
551
+ "affected_artifacts": {
552
+ "type": "array",
553
+ "items": { "type": "string", "nonEmpty": true },
554
+ "minItems": 1
555
+ }
556
+ },
557
+ "required": ["title", "description", "affected_artifacts"]
558
+ }
559
+ },
560
+ "consistency_checks": {
561
+ "type": "array",
562
+ "items": {
563
+ "type": "object",
564
+ "properties": {
565
+ "name": { "type": "string", "nonEmpty": true },
566
+ "status": { "type": "string", "nonEmpty": true },
567
+ "details": { "type": "string", "nonEmpty": true }
568
+ },
569
+ "required": ["name", "status", "details"]
570
+ }
571
+ },
572
+ "qa_coverage_gaps": {
573
+ "type": "array",
574
+ "items": {
575
+ "type": "object",
576
+ "properties": {
577
+ "title": { "type": "string", "nonEmpty": true },
578
+ "description": { "type": "string", "nonEmpty": true },
579
+ "affected_artifacts": {
580
+ "type": "array",
581
+ "items": { "type": "string", "nonEmpty": true },
582
+ "minItems": 1
583
+ }
584
+ },
585
+ "required": ["title", "description", "affected_artifacts"]
586
+ }
587
+ },
588
+ "recommended_actions": {
589
+ "type": "array",
590
+ "items": { "type": "string", "nonEmpty": true }
591
+ }
592
+ },
593
+ "required": [
594
+ "summary",
595
+ "status",
596
+ "blocking_findings",
597
+ "major_findings",
598
+ "warnings",
599
+ "missing_information",
600
+ "consistency_checks",
601
+ "qa_coverage_gaps",
602
+ "recommended_actions"
603
+ ]
604
+ },
488
605
  "review-findings/v1": {
489
606
  "type": "object",
490
607
  "properties": {
@@ -11,6 +11,9 @@ function schemaLabel(node) {
11
11
  }
12
12
  switch (node.type) {
13
13
  case "string":
14
+ if (node.enum && node.enum.length > 0) {
15
+ return `one of: ${node.enum.join(", ")}`;
16
+ }
14
17
  return node.nonEmpty ? "a non-empty string" : "a string";
15
18
  case "boolean":
16
19
  return "a boolean";
@@ -43,6 +46,9 @@ function validateNode(value, schema, currentPath) {
43
46
  if (typeof value !== "string" || (schema.nonEmpty && value.trim().length === 0)) {
44
47
  return [`${currentPath} must be ${schemaLabel(schema)}`];
45
48
  }
49
+ if (schema.enum && !schema.enum.includes(value)) {
50
+ return [`${currentPath} must be ${schemaLabel(schema)}`];
51
+ }
46
52
  return [];
47
53
  case "boolean":
48
54
  return typeof value === "boolean" ? [] : [`${currentPath} must be a boolean`];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentweaver",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "CLI orchestrator for Jira/Codex engineering workflows",
5
5
  "keywords": [
6
6
  "agent",