@tagma/sdk 0.6.0 → 0.6.2
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/LICENSE +21 -21
- package/README.md +573 -573
- package/dist/bootstrap.d.ts +11 -1
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +18 -9
- package/dist/bootstrap.js.map +1 -1
- package/dist/drivers/opencode.d.ts.map +1 -1
- package/dist/drivers/opencode.js +47 -17
- package/dist/drivers/opencode.js.map +1 -1
- package/dist/engine.d.ts +8 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +17 -16
- package/dist/engine.js.map +1 -1
- package/dist/plugin-registry.test.d.ts +2 -0
- package/dist/plugin-registry.test.d.ts.map +1 -0
- package/dist/plugin-registry.test.js +188 -0
- package/dist/plugin-registry.test.js.map +1 -0
- package/dist/registry.d.ts +52 -28
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +126 -91
- package/dist/registry.js.map +1 -1
- package/dist/sdk.d.ts +1 -1
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +1 -1
- package/dist/sdk.js.map +1 -1
- package/package.json +2 -2
- package/src/bootstrap.ts +46 -37
- package/src/completions/output-check.ts +92 -92
- package/src/dag.ts +245 -245
- package/src/drivers/opencode.ts +410 -371
- package/src/engine.ts +1228 -1220
- package/src/hooks.ts +193 -193
- package/src/middlewares/static-context.ts +49 -49
- package/src/pipeline-runner.ts +173 -173
- package/src/plugin-registry.test.ts +230 -0
- package/src/prompt-doc.ts +49 -49
- package/src/registry.ts +316 -267
- package/src/runner.ts +460 -460
- package/src/schema.test.ts +101 -101
- package/src/schema.ts +338 -338
- package/src/sdk.ts +120 -118
- package/src/task-ref.test.ts +401 -401
- package/src/task-ref.ts +120 -120
- package/src/validate-raw.ts +412 -412
- package/dist/drivers/claude-code.d.ts +0 -3
- package/dist/drivers/claude-code.d.ts.map +0 -1
- package/dist/drivers/claude-code.js +0 -225
- package/dist/drivers/claude-code.js.map +0 -1
package/src/task-ref.ts
CHANGED
|
@@ -1,120 +1,120 @@
|
|
|
1
|
-
// ═══ Task reference resolution — single source of truth ═══
|
|
2
|
-
//
|
|
3
|
-
// Before this module existed, four sites each carried their own copy of the
|
|
4
|
-
// "what is a valid id" + "how do I resolve a bare / same-track-shorthand /
|
|
5
|
-
// fully qualified ref" logic:
|
|
6
|
-
//
|
|
7
|
-
// - dag.ts/buildDag (threw on unresolved, threw on ambiguous)
|
|
8
|
-
// - dag.ts/buildRawDag (silently skipped unresolved and ambiguous)
|
|
9
|
-
// - validate-raw.ts (reported both as errors, with different wording)
|
|
10
|
-
// - engine.ts/resolveRefInDag (returned null on ambiguous)
|
|
11
|
-
//
|
|
12
|
-
// In addition, the editor shipped its own regex for id validation in
|
|
13
|
-
// `shared/config-id.ts` and a test-local copy in `config-id-generation.test.ts`,
|
|
14
|
-
// creating multiple places where the character set could drift from the
|
|
15
|
-
// validator. Bugs observed downstream (silent context loss when two tracks
|
|
16
|
-
// happened to share a bare task name; editor-generated ids occasionally
|
|
17
|
-
// failing SDK validate-raw) all traced back to this duplication.
|
|
18
|
-
//
|
|
19
|
-
// Callers now build a TaskIndex once and use `resolveTaskRef` to classify
|
|
20
|
-
// each reference, then decide themselves whether to throw / warn / skip —
|
|
21
|
-
// instead of re-implementing the index build and the lookup logic.
|
|
22
|
-
|
|
23
|
-
import type { PipelineConfig, RawPipelineConfig } from './types';
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* D8: task and track ids must match this pattern. No dots: the `.` is the
|
|
27
|
-
* qualified-id separator ("trackId.taskId"), so allowing it inside either
|
|
28
|
-
* part would make qid parsing ambiguous and break every resolver below.
|
|
29
|
-
*/
|
|
30
|
-
export const TASK_ID_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
31
|
-
|
|
32
|
-
export function isValidTaskId(id: unknown): id is string {
|
|
33
|
-
return typeof id === 'string' && TASK_ID_RE.test(id);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Canonical qualified form used throughout the engine. */
|
|
37
|
-
export function qualifyTaskId(trackId: string, taskId: string): string {
|
|
38
|
-
return `${trackId}.${taskId}`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** Does the reference already include a track prefix? */
|
|
42
|
-
export function isQualifiedRef(ref: string): boolean {
|
|
43
|
-
return ref.includes('.');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Sentinel stored in `TaskIndex.bareToQualified` when a bare task id is
|
|
48
|
-
* shared by more than one track, making it unresolvable without a prefix.
|
|
49
|
-
* Exposed so callers that want to inspect the index directly know what to
|
|
50
|
-
* look for — but prefer `resolveTaskRef` which returns a typed `kind`.
|
|
51
|
-
*/
|
|
52
|
-
export const AMBIGUOUS = '__ambiguous__';
|
|
53
|
-
|
|
54
|
-
export interface TaskIndex {
|
|
55
|
-
/** All fully-qualified ids ("trackId.taskId") present in the config. */
|
|
56
|
-
readonly allQualified: ReadonlySet<string>;
|
|
57
|
-
/** bare taskId → qid, or the {@link AMBIGUOUS} sentinel. */
|
|
58
|
-
readonly bareToQualified: ReadonlyMap<string, string>;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Build the index used by {@link resolveTaskRef}. Tolerant of partially
|
|
63
|
-
* malformed configs: tracks or tasks missing an `id` are skipped so the
|
|
64
|
-
* editor can call this during real-time validation on in-progress edits.
|
|
65
|
-
*/
|
|
66
|
-
export function buildTaskIndex(config: RawPipelineConfig | PipelineConfig): TaskIndex {
|
|
67
|
-
const allQualified = new Set<string>();
|
|
68
|
-
const bareToQualified = new Map<string, string>();
|
|
69
|
-
for (const track of config.tracks ?? []) {
|
|
70
|
-
if (!track?.id) continue;
|
|
71
|
-
for (const task of track.tasks ?? []) {
|
|
72
|
-
if (!task?.id) continue;
|
|
73
|
-
const qid = qualifyTaskId(track.id, task.id);
|
|
74
|
-
allQualified.add(qid);
|
|
75
|
-
if (bareToQualified.has(task.id)) {
|
|
76
|
-
bareToQualified.set(task.id, AMBIGUOUS);
|
|
77
|
-
} else {
|
|
78
|
-
bareToQualified.set(task.id, qid);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return { allQualified, bareToQualified };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export type RefResolution =
|
|
86
|
-
| { readonly kind: 'resolved'; readonly qid: string }
|
|
87
|
-
| { readonly kind: 'ambiguous'; readonly ref: string }
|
|
88
|
-
| { readonly kind: 'not_found'; readonly ref: string };
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Resolve a dependency / continue_from reference to a canonical qid.
|
|
92
|
-
*
|
|
93
|
-
* 1. If the ref already contains a `.`, treat it as fully qualified —
|
|
94
|
-
* return `resolved` when the qid exists, `not_found` otherwise.
|
|
95
|
-
* 2. Otherwise, prefer the same-track shorthand (`fromTrackId.ref`).
|
|
96
|
-
* 3. Fall back to a global bare lookup. Returns `ambiguous` when more
|
|
97
|
-
* than one track has a task with that bare name.
|
|
98
|
-
*
|
|
99
|
-
* Callers decide the policy: `buildDag` throws on non-resolved, `buildRawDag`
|
|
100
|
-
* skips silently, `validateRaw` emits a structured ValidationError.
|
|
101
|
-
*/
|
|
102
|
-
export function resolveTaskRef(
|
|
103
|
-
ref: string,
|
|
104
|
-
fromTrackId: string,
|
|
105
|
-
index: TaskIndex,
|
|
106
|
-
): RefResolution {
|
|
107
|
-
if (isQualifiedRef(ref)) {
|
|
108
|
-
return index.allQualified.has(ref)
|
|
109
|
-
? { kind: 'resolved', qid: ref }
|
|
110
|
-
: { kind: 'not_found', ref };
|
|
111
|
-
}
|
|
112
|
-
const sameTrack = qualifyTaskId(fromTrackId, ref);
|
|
113
|
-
if (index.allQualified.has(sameTrack)) {
|
|
114
|
-
return { kind: 'resolved', qid: sameTrack };
|
|
115
|
-
}
|
|
116
|
-
const global = index.bareToQualified.get(ref);
|
|
117
|
-
if (global === AMBIGUOUS) return { kind: 'ambiguous', ref };
|
|
118
|
-
if (global !== undefined) return { kind: 'resolved', qid: global };
|
|
119
|
-
return { kind: 'not_found', ref };
|
|
120
|
-
}
|
|
1
|
+
// ═══ Task reference resolution — single source of truth ═══
|
|
2
|
+
//
|
|
3
|
+
// Before this module existed, four sites each carried their own copy of the
|
|
4
|
+
// "what is a valid id" + "how do I resolve a bare / same-track-shorthand /
|
|
5
|
+
// fully qualified ref" logic:
|
|
6
|
+
//
|
|
7
|
+
// - dag.ts/buildDag (threw on unresolved, threw on ambiguous)
|
|
8
|
+
// - dag.ts/buildRawDag (silently skipped unresolved and ambiguous)
|
|
9
|
+
// - validate-raw.ts (reported both as errors, with different wording)
|
|
10
|
+
// - engine.ts/resolveRefInDag (returned null on ambiguous)
|
|
11
|
+
//
|
|
12
|
+
// In addition, the editor shipped its own regex for id validation in
|
|
13
|
+
// `shared/config-id.ts` and a test-local copy in `config-id-generation.test.ts`,
|
|
14
|
+
// creating multiple places where the character set could drift from the
|
|
15
|
+
// validator. Bugs observed downstream (silent context loss when two tracks
|
|
16
|
+
// happened to share a bare task name; editor-generated ids occasionally
|
|
17
|
+
// failing SDK validate-raw) all traced back to this duplication.
|
|
18
|
+
//
|
|
19
|
+
// Callers now build a TaskIndex once and use `resolveTaskRef` to classify
|
|
20
|
+
// each reference, then decide themselves whether to throw / warn / skip —
|
|
21
|
+
// instead of re-implementing the index build and the lookup logic.
|
|
22
|
+
|
|
23
|
+
import type { PipelineConfig, RawPipelineConfig } from './types';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* D8: task and track ids must match this pattern. No dots: the `.` is the
|
|
27
|
+
* qualified-id separator ("trackId.taskId"), so allowing it inside either
|
|
28
|
+
* part would make qid parsing ambiguous and break every resolver below.
|
|
29
|
+
*/
|
|
30
|
+
export const TASK_ID_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
31
|
+
|
|
32
|
+
export function isValidTaskId(id: unknown): id is string {
|
|
33
|
+
return typeof id === 'string' && TASK_ID_RE.test(id);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Canonical qualified form used throughout the engine. */
|
|
37
|
+
export function qualifyTaskId(trackId: string, taskId: string): string {
|
|
38
|
+
return `${trackId}.${taskId}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Does the reference already include a track prefix? */
|
|
42
|
+
export function isQualifiedRef(ref: string): boolean {
|
|
43
|
+
return ref.includes('.');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Sentinel stored in `TaskIndex.bareToQualified` when a bare task id is
|
|
48
|
+
* shared by more than one track, making it unresolvable without a prefix.
|
|
49
|
+
* Exposed so callers that want to inspect the index directly know what to
|
|
50
|
+
* look for — but prefer `resolveTaskRef` which returns a typed `kind`.
|
|
51
|
+
*/
|
|
52
|
+
export const AMBIGUOUS = '__ambiguous__';
|
|
53
|
+
|
|
54
|
+
export interface TaskIndex {
|
|
55
|
+
/** All fully-qualified ids ("trackId.taskId") present in the config. */
|
|
56
|
+
readonly allQualified: ReadonlySet<string>;
|
|
57
|
+
/** bare taskId → qid, or the {@link AMBIGUOUS} sentinel. */
|
|
58
|
+
readonly bareToQualified: ReadonlyMap<string, string>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Build the index used by {@link resolveTaskRef}. Tolerant of partially
|
|
63
|
+
* malformed configs: tracks or tasks missing an `id` are skipped so the
|
|
64
|
+
* editor can call this during real-time validation on in-progress edits.
|
|
65
|
+
*/
|
|
66
|
+
export function buildTaskIndex(config: RawPipelineConfig | PipelineConfig): TaskIndex {
|
|
67
|
+
const allQualified = new Set<string>();
|
|
68
|
+
const bareToQualified = new Map<string, string>();
|
|
69
|
+
for (const track of config.tracks ?? []) {
|
|
70
|
+
if (!track?.id) continue;
|
|
71
|
+
for (const task of track.tasks ?? []) {
|
|
72
|
+
if (!task?.id) continue;
|
|
73
|
+
const qid = qualifyTaskId(track.id, task.id);
|
|
74
|
+
allQualified.add(qid);
|
|
75
|
+
if (bareToQualified.has(task.id)) {
|
|
76
|
+
bareToQualified.set(task.id, AMBIGUOUS);
|
|
77
|
+
} else {
|
|
78
|
+
bareToQualified.set(task.id, qid);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { allQualified, bareToQualified };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export type RefResolution =
|
|
86
|
+
| { readonly kind: 'resolved'; readonly qid: string }
|
|
87
|
+
| { readonly kind: 'ambiguous'; readonly ref: string }
|
|
88
|
+
| { readonly kind: 'not_found'; readonly ref: string };
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Resolve a dependency / continue_from reference to a canonical qid.
|
|
92
|
+
*
|
|
93
|
+
* 1. If the ref already contains a `.`, treat it as fully qualified —
|
|
94
|
+
* return `resolved` when the qid exists, `not_found` otherwise.
|
|
95
|
+
* 2. Otherwise, prefer the same-track shorthand (`fromTrackId.ref`).
|
|
96
|
+
* 3. Fall back to a global bare lookup. Returns `ambiguous` when more
|
|
97
|
+
* than one track has a task with that bare name.
|
|
98
|
+
*
|
|
99
|
+
* Callers decide the policy: `buildDag` throws on non-resolved, `buildRawDag`
|
|
100
|
+
* skips silently, `validateRaw` emits a structured ValidationError.
|
|
101
|
+
*/
|
|
102
|
+
export function resolveTaskRef(
|
|
103
|
+
ref: string,
|
|
104
|
+
fromTrackId: string,
|
|
105
|
+
index: TaskIndex,
|
|
106
|
+
): RefResolution {
|
|
107
|
+
if (isQualifiedRef(ref)) {
|
|
108
|
+
return index.allQualified.has(ref)
|
|
109
|
+
? { kind: 'resolved', qid: ref }
|
|
110
|
+
: { kind: 'not_found', ref };
|
|
111
|
+
}
|
|
112
|
+
const sameTrack = qualifyTaskId(fromTrackId, ref);
|
|
113
|
+
if (index.allQualified.has(sameTrack)) {
|
|
114
|
+
return { kind: 'resolved', qid: sameTrack };
|
|
115
|
+
}
|
|
116
|
+
const global = index.bareToQualified.get(ref);
|
|
117
|
+
if (global === AMBIGUOUS) return { kind: 'ambiguous', ref };
|
|
118
|
+
if (global !== undefined) return { kind: 'resolved', qid: global };
|
|
119
|
+
return { kind: 'not_found', ref };
|
|
120
|
+
}
|