openplanr 1.2.7 → 1.3.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/README.md +41 -4
- package/dist/agents/task-parser.d.ts.map +1 -1
- package/dist/agents/task-parser.js +8 -34
- package/dist/agents/task-parser.js.map +1 -1
- package/dist/ai/prompts/prompt-builder.d.ts +48 -0
- package/dist/ai/prompts/prompt-builder.d.ts.map +1 -1
- package/dist/ai/prompts/prompt-builder.js +57 -1
- package/dist/ai/prompts/prompt-builder.js.map +1 -1
- package/dist/ai/prompts/system-prompts.d.ts +24 -1
- package/dist/ai/prompts/system-prompts.d.ts.map +1 -1
- package/dist/ai/prompts/system-prompts.js +104 -6
- package/dist/ai/prompts/system-prompts.js.map +1 -1
- package/dist/ai/schemas/ai-response-schemas.d.ts +68 -0
- package/dist/ai/schemas/ai-response-schemas.d.ts.map +1 -1
- package/dist/ai/schemas/ai-response-schemas.js +81 -0
- package/dist/ai/schemas/ai-response-schemas.js.map +1 -1
- package/dist/ai/types.d.ts +2 -0
- package/dist/ai/types.d.ts.map +1 -1
- package/dist/ai/types.js +4 -0
- package/dist/ai/types.js.map +1 -1
- package/dist/cli/commands/backlog.d.ts +12 -0
- package/dist/cli/commands/backlog.d.ts.map +1 -1
- package/dist/cli/commands/backlog.js +88 -2
- package/dist/cli/commands/backlog.js.map +1 -1
- package/dist/cli/commands/config.d.ts.map +1 -1
- package/dist/cli/commands/config.js +8 -2
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/linear.d.ts +8 -0
- package/dist/cli/commands/linear.d.ts.map +1 -0
- package/dist/cli/commands/linear.js +550 -0
- package/dist/cli/commands/linear.js.map +1 -0
- package/dist/cli/commands/quick.d.ts +17 -0
- package/dist/cli/commands/quick.d.ts.map +1 -1
- package/dist/cli/commands/quick.js +31 -15
- package/dist/cli/commands/quick.js.map +1 -1
- package/dist/cli/commands/revise.d.ts +24 -0
- package/dist/cli/commands/revise.d.ts.map +1 -0
- package/dist/cli/commands/revise.js +570 -0
- package/dist/cli/commands/revise.js.map +1 -0
- package/dist/cli/index.js +4 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/models/schema.d.ts +43 -0
- package/dist/models/schema.d.ts.map +1 -1
- package/dist/models/schema.js +49 -0
- package/dist/models/schema.js.map +1 -1
- package/dist/models/types.d.ts +296 -0
- package/dist/models/types.d.ts.map +1 -1
- package/dist/services/artifact-gathering.d.ts +4 -0
- package/dist/services/artifact-gathering.d.ts.map +1 -1
- package/dist/services/artifact-gathering.js +1 -1
- package/dist/services/artifact-gathering.js.map +1 -1
- package/dist/services/artifact-service.d.ts +12 -1
- package/dist/services/artifact-service.d.ts.map +1 -1
- package/dist/services/artifact-service.js +49 -6
- package/dist/services/artifact-service.js.map +1 -1
- package/dist/services/atomic-write-service.d.ts +41 -0
- package/dist/services/atomic-write-service.d.ts.map +1 -0
- package/dist/services/atomic-write-service.js +87 -0
- package/dist/services/atomic-write-service.js.map +1 -0
- package/dist/services/audit-log-service.d.ts +47 -0
- package/dist/services/audit-log-service.d.ts.map +1 -0
- package/dist/services/audit-log-service.js +210 -0
- package/dist/services/audit-log-service.js.map +1 -0
- package/dist/services/cascade-service.d.ts +62 -0
- package/dist/services/cascade-service.d.ts.map +1 -0
- package/dist/services/cascade-service.js +189 -0
- package/dist/services/cascade-service.js.map +1 -0
- package/dist/services/credentials-service.js +2 -2
- package/dist/services/credentials-service.js.map +1 -1
- package/dist/services/diff-service.d.ts +18 -0
- package/dist/services/diff-service.d.ts.map +1 -0
- package/dist/services/diff-service.js +35 -0
- package/dist/services/diff-service.js.map +1 -0
- package/dist/services/evidence-verifier.d.ts +71 -0
- package/dist/services/evidence-verifier.d.ts.map +1 -0
- package/dist/services/evidence-verifier.js +174 -0
- package/dist/services/evidence-verifier.js.map +1 -0
- package/dist/services/git-service.d.ts +60 -0
- package/dist/services/git-service.d.ts.map +1 -0
- package/dist/services/git-service.js +137 -0
- package/dist/services/git-service.js.map +1 -0
- package/dist/services/graph-integrity.d.ts +35 -0
- package/dist/services/graph-integrity.d.ts.map +1 -0
- package/dist/services/graph-integrity.js +53 -0
- package/dist/services/graph-integrity.js.map +1 -0
- package/dist/services/linear/body-formatters.d.ts +69 -0
- package/dist/services/linear/body-formatters.d.ts.map +1 -0
- package/dist/services/linear/body-formatters.js +183 -0
- package/dist/services/linear/body-formatters.js.map +1 -0
- package/dist/services/linear/constants.d.ts +61 -0
- package/dist/services/linear/constants.d.ts.map +1 -0
- package/dist/services/linear/constants.js +84 -0
- package/dist/services/linear/constants.js.map +1 -0
- package/dist/services/linear/errors.d.ts +14 -0
- package/dist/services/linear/errors.d.ts.map +1 -0
- package/dist/services/linear/errors.js +106 -0
- package/dist/services/linear/errors.js.map +1 -0
- package/dist/services/linear/estimate-resolver.d.ts +50 -0
- package/dist/services/linear/estimate-resolver.d.ts.map +1 -0
- package/dist/services/linear/estimate-resolver.js +82 -0
- package/dist/services/linear/estimate-resolver.js.map +1 -0
- package/dist/services/linear/plan-builders.d.ts +64 -0
- package/dist/services/linear/plan-builders.d.ts.map +1 -0
- package/dist/services/linear/plan-builders.js +237 -0
- package/dist/services/linear/plan-builders.js.map +1 -0
- package/dist/services/linear/scope-loaders.d.ts +79 -0
- package/dist/services/linear/scope-loaders.d.ts.map +1 -0
- package/dist/services/linear/scope-loaders.js +227 -0
- package/dist/services/linear/scope-loaders.js.map +1 -0
- package/dist/services/linear/strategy-context.d.ts +66 -0
- package/dist/services/linear/strategy-context.d.ts.map +1 -0
- package/dist/services/linear/strategy-context.js +121 -0
- package/dist/services/linear/strategy-context.js.map +1 -0
- package/dist/services/linear-mapping-service.d.ts +11 -0
- package/dist/services/linear-mapping-service.d.ts.map +1 -0
- package/dist/services/linear-mapping-service.js +220 -0
- package/dist/services/linear-mapping-service.js.map +1 -0
- package/dist/services/linear-pull-service.d.ts +137 -0
- package/dist/services/linear-pull-service.d.ts.map +1 -0
- package/dist/services/linear-pull-service.js +720 -0
- package/dist/services/linear-pull-service.js.map +1 -0
- package/dist/services/linear-push-service.d.ts +86 -0
- package/dist/services/linear-push-service.d.ts.map +1 -0
- package/dist/services/linear-push-service.js +956 -0
- package/dist/services/linear-push-service.js.map +1 -0
- package/dist/services/linear-service.d.ts +122 -0
- package/dist/services/linear-service.d.ts.map +1 -0
- package/dist/services/linear-service.js +361 -0
- package/dist/services/linear-service.js.map +1 -0
- package/dist/services/prompt-service.d.ts +37 -0
- package/dist/services/prompt-service.d.ts.map +1 -1
- package/dist/services/prompt-service.js +111 -0
- package/dist/services/prompt-service.js.map +1 -1
- package/dist/services/revise-apply-service.d.ts +55 -0
- package/dist/services/revise-apply-service.d.ts.map +1 -0
- package/dist/services/revise-apply-service.js +255 -0
- package/dist/services/revise-apply-service.js.map +1 -0
- package/dist/services/revise-cache-service.d.ts +46 -0
- package/dist/services/revise-cache-service.d.ts.map +1 -0
- package/dist/services/revise-cache-service.js +88 -0
- package/dist/services/revise-cache-service.js.map +1 -0
- package/dist/services/revise-plan-service.d.ts +38 -0
- package/dist/services/revise-plan-service.d.ts.map +1 -0
- package/dist/services/revise-plan-service.js +151 -0
- package/dist/services/revise-plan-service.js.map +1 -0
- package/dist/services/revise-service.d.ts +115 -0
- package/dist/services/revise-service.d.ts.map +1 -0
- package/dist/services/revise-service.js +294 -0
- package/dist/services/revise-service.js.map +1 -0
- package/dist/services/template-sections.d.ts +28 -0
- package/dist/services/template-sections.d.ts.map +1 -0
- package/dist/services/template-sections.js +55 -0
- package/dist/services/template-sections.js.map +1 -0
- package/dist/templates/backlog/backlog-item.md.hbs +3 -0
- package/dist/templates/quick/quick-task.md.hbs +6 -0
- package/dist/utils/diff.d.ts +47 -0
- package/dist/utils/diff.d.ts.map +1 -0
- package/dist/utils/diff.js +278 -0
- package/dist/utils/diff.js.map +1 -0
- package/dist/utils/markdown.d.ts +23 -0
- package/dist/utils/markdown.d.ts.map +1 -1
- package/dist/utils/markdown.js +79 -0
- package/dist/utils/markdown.js.map +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git integration for `planr revise`.
|
|
3
|
+
*
|
|
4
|
+
* Two responsibilities:
|
|
5
|
+
*
|
|
6
|
+
* 1. **Clean-tree gate:** revise refuses to run with a dirty
|
|
7
|
+
* working tree by default. Users can override with `--allow-dirty`, but
|
|
8
|
+
* post-flight rollback depends on a clean pre-run state, so the gate is
|
|
9
|
+
* the load-bearing safety net.
|
|
10
|
+
*
|
|
11
|
+
* 2. **Capture + rollback anchor:** before bulk writes,
|
|
12
|
+
* revise captures HEAD and the set of touched paths so a post-flight
|
|
13
|
+
* graph-integrity failure can restore via `git checkout`.
|
|
14
|
+
*
|
|
15
|
+
* All git operations use `execFile` (not shell), matching the pattern in
|
|
16
|
+
* github-service.ts. If git is not available or the project is not a git
|
|
17
|
+
* repo, clean-tree checks fail closed (revise refuses to run) unless
|
|
18
|
+
* --allow-dirty is passed, because without git there is no safety net.
|
|
19
|
+
*/
|
|
20
|
+
import { execFile } from 'node:child_process';
|
|
21
|
+
import { promisify } from 'node:util';
|
|
22
|
+
const execFileAsync = promisify(execFile);
|
|
23
|
+
const GIT_MAX_BUFFER = 10 * 1024 * 1024;
|
|
24
|
+
/**
|
|
25
|
+
* Inspect the working tree. Never throws — always returns a typed status so
|
|
26
|
+
* callers can render errors consistently.
|
|
27
|
+
*/
|
|
28
|
+
export async function inspectGitTree(projectDir) {
|
|
29
|
+
let head;
|
|
30
|
+
try {
|
|
31
|
+
const { stdout } = await execFileAsync('git', ['rev-parse', 'HEAD'], {
|
|
32
|
+
cwd: projectDir,
|
|
33
|
+
maxBuffer: GIT_MAX_BUFFER,
|
|
34
|
+
});
|
|
35
|
+
head = stdout.trim();
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
39
|
+
if (/not a git repository|fatal:.*repository/i.test(message)) {
|
|
40
|
+
return { kind: 'not-a-repo', reason: message };
|
|
41
|
+
}
|
|
42
|
+
if (/ENOENT|git: not found|command not found/i.test(message)) {
|
|
43
|
+
return { kind: 'git-missing', reason: message };
|
|
44
|
+
}
|
|
45
|
+
// Empty repo (no commits yet) also reaches here; treat it as not-a-repo
|
|
46
|
+
// for clean-tree gating purposes — there is no HEAD to roll back to.
|
|
47
|
+
if (/unknown revision|does not have any commits/i.test(message)) {
|
|
48
|
+
return { kind: 'not-a-repo', reason: 'git repository has no commits yet' };
|
|
49
|
+
}
|
|
50
|
+
return { kind: 'not-a-repo', reason: message };
|
|
51
|
+
}
|
|
52
|
+
const { stdout: porcelain } = await execFileAsync('git', ['status', '--porcelain'], {
|
|
53
|
+
cwd: projectDir,
|
|
54
|
+
maxBuffer: GIT_MAX_BUFFER,
|
|
55
|
+
});
|
|
56
|
+
if (porcelain.trim().length === 0) {
|
|
57
|
+
return { kind: 'clean', head };
|
|
58
|
+
}
|
|
59
|
+
const changedPaths = porcelain
|
|
60
|
+
.split('\n')
|
|
61
|
+
.filter((line) => line.length > 0)
|
|
62
|
+
.map((line) => line.slice(3).trim());
|
|
63
|
+
return { kind: 'dirty', head, changedPaths };
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The clean-tree gate: clean → pass; dirty → pass only when --allow-dirty;
|
|
67
|
+
* missing git / not a repo → fail closed unless --allow-dirty was passed
|
|
68
|
+
* (because without git there is no post-flight rollback safety net).
|
|
69
|
+
*/
|
|
70
|
+
export async function checkCleanTree(projectDir, options) {
|
|
71
|
+
const status = await inspectGitTree(projectDir);
|
|
72
|
+
switch (status.kind) {
|
|
73
|
+
case 'clean':
|
|
74
|
+
return {
|
|
75
|
+
ok: true,
|
|
76
|
+
status,
|
|
77
|
+
message: `Working tree is clean at ${status.head.slice(0, 12)}.`,
|
|
78
|
+
};
|
|
79
|
+
case 'dirty':
|
|
80
|
+
if (options.allowDirty) {
|
|
81
|
+
return {
|
|
82
|
+
ok: true,
|
|
83
|
+
status,
|
|
84
|
+
message: `Working tree has ${status.changedPaths.length} uncommitted change(s); running with --allow-dirty. Post-flight rollback cannot restore these changes.`,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
ok: false,
|
|
89
|
+
status,
|
|
90
|
+
message: `Working tree has ${status.changedPaths.length} uncommitted change(s). Commit or stash them, or re-run with --allow-dirty (post-flight rollback cannot restore uncommitted work).`,
|
|
91
|
+
};
|
|
92
|
+
case 'not-a-repo':
|
|
93
|
+
if (options.allowDirty) {
|
|
94
|
+
return {
|
|
95
|
+
ok: true,
|
|
96
|
+
status,
|
|
97
|
+
message: `Not a git repository (${status.reason.trim()}); running with --allow-dirty. Post-flight rollback is disabled.`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
ok: false,
|
|
102
|
+
status,
|
|
103
|
+
message: `Not a git repository (${status.reason.trim()}). Revise requires git for its post-flight rollback safety net. Initialize git, or re-run with --allow-dirty to opt out of the safety net.`,
|
|
104
|
+
};
|
|
105
|
+
case 'git-missing':
|
|
106
|
+
if (options.allowDirty) {
|
|
107
|
+
return {
|
|
108
|
+
ok: true,
|
|
109
|
+
status,
|
|
110
|
+
message: `git CLI not found; running with --allow-dirty. Post-flight rollback is disabled.`,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
ok: false,
|
|
115
|
+
status,
|
|
116
|
+
message: `git CLI not found on PATH. Revise requires git for its post-flight rollback safety net. Install git, or re-run with --allow-dirty to opt out.`,
|
|
117
|
+
};
|
|
118
|
+
default: {
|
|
119
|
+
const _exhaustive = status;
|
|
120
|
+
throw new Error(`unhandled GitTreeStatus: ${JSON.stringify(_exhaustive)}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Restore a set of paths from HEAD — the primitive the post-flight
|
|
126
|
+
* rollback invokes when graph integrity breaks after writes. Paths are
|
|
127
|
+
* relative to `projectDir`. Empty list is a no-op.
|
|
128
|
+
*/
|
|
129
|
+
export async function checkoutPaths(projectDir, relativePaths) {
|
|
130
|
+
if (relativePaths.length === 0)
|
|
131
|
+
return;
|
|
132
|
+
await execFileAsync('git', ['checkout', '--', ...relativePaths], {
|
|
133
|
+
cwd: projectDir,
|
|
134
|
+
maxBuffer: GIT_MAX_BUFFER,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=git-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-service.js","sourceRoot":"","sources":["../../src/services/git-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only artifact-graph integrity check for `planr revise` post-flight.
|
|
3
|
+
*
|
|
4
|
+
* Detects broken parent/child links after a revise run so the caller can
|
|
5
|
+
* trigger automatic rollback if the writes left the tree inconsistent. This
|
|
6
|
+
* is deliberately narrower than `planr sync`: it does not fix anything, does
|
|
7
|
+
* not write to the logger, and only looks at the relationships revise might
|
|
8
|
+
* have perturbed (parent-id frontmatter fields on features, stories, tasks).
|
|
9
|
+
*
|
|
10
|
+
* Why a separate module: keeps revise decoupled from the `sync` command's
|
|
11
|
+
* repair logic, which has different invariants (fixes links, prompts the
|
|
12
|
+
* user, etc.). The check here is a pure data function.
|
|
13
|
+
*/
|
|
14
|
+
import type { ArtifactType, OpenPlanrConfig } from '../models/types.js';
|
|
15
|
+
export interface GraphIntegrityIssue {
|
|
16
|
+
childId: string;
|
|
17
|
+
childType: ArtifactType;
|
|
18
|
+
childPath: string;
|
|
19
|
+
parentField: 'epicId' | 'featureId' | 'storyId';
|
|
20
|
+
parentId: string;
|
|
21
|
+
reason: 'missing-parent' | 'parent-type-mismatch';
|
|
22
|
+
}
|
|
23
|
+
export interface GraphIntegrityReport {
|
|
24
|
+
ok: boolean;
|
|
25
|
+
issues: GraphIntegrityIssue[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Walk every feature/story/task and verify its parent-id frontmatter
|
|
29
|
+
* resolves to an existing parent artifact of the correct type. Missing
|
|
30
|
+
* parent-id fields are tolerated (some tasks legitimately attach at
|
|
31
|
+
* feature level, for example) — only *broken* non-empty references are
|
|
32
|
+
* reported.
|
|
33
|
+
*/
|
|
34
|
+
export declare function checkGraphIntegrity(projectDir: string, config: OpenPlanrConfig): Promise<GraphIntegrityReport>;
|
|
35
|
+
//# sourceMappingURL=graph-integrity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-integrity.d.ts","sourceRoot":"","sources":["../../src/services/graph-integrity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAGxE,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,YAAY,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,QAAQ,GAAG,WAAW,GAAG,SAAS,CAAC;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,gBAAgB,GAAG,sBAAsB,CAAC;CACnD;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,mBAAmB,EAAE,CAAC;CAC/B;AAcD;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,oBAAoB,CAAC,CA0B/B"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only artifact-graph integrity check for `planr revise` post-flight.
|
|
3
|
+
*
|
|
4
|
+
* Detects broken parent/child links after a revise run so the caller can
|
|
5
|
+
* trigger automatic rollback if the writes left the tree inconsistent. This
|
|
6
|
+
* is deliberately narrower than `planr sync`: it does not fix anything, does
|
|
7
|
+
* not write to the logger, and only looks at the relationships revise might
|
|
8
|
+
* have perturbed (parent-id frontmatter fields on features, stories, tasks).
|
|
9
|
+
*
|
|
10
|
+
* Why a separate module: keeps revise decoupled from the `sync` command's
|
|
11
|
+
* repair logic, which has different invariants (fixes links, prompts the
|
|
12
|
+
* user, etc.). The check here is a pure data function.
|
|
13
|
+
*/
|
|
14
|
+
import { listArtifacts, readArtifact } from './artifact-service.js';
|
|
15
|
+
const CHECKS = [
|
|
16
|
+
{ childType: 'feature', parentField: 'epicId', parentType: 'epic' },
|
|
17
|
+
{ childType: 'story', parentField: 'featureId', parentType: 'feature' },
|
|
18
|
+
{ childType: 'task', parentField: 'storyId', parentType: 'story' },
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Walk every feature/story/task and verify its parent-id frontmatter
|
|
22
|
+
* resolves to an existing parent artifact of the correct type. Missing
|
|
23
|
+
* parent-id fields are tolerated (some tasks legitimately attach at
|
|
24
|
+
* feature level, for example) — only *broken* non-empty references are
|
|
25
|
+
* reported.
|
|
26
|
+
*/
|
|
27
|
+
export async function checkGraphIntegrity(projectDir, config) {
|
|
28
|
+
const issues = [];
|
|
29
|
+
for (const spec of CHECKS) {
|
|
30
|
+
const children = await listArtifacts(projectDir, config, spec.childType);
|
|
31
|
+
for (const childRow of children) {
|
|
32
|
+
const child = await readArtifact(projectDir, config, spec.childType, childRow.id);
|
|
33
|
+
if (!child)
|
|
34
|
+
continue;
|
|
35
|
+
const parentId = child.data[spec.parentField];
|
|
36
|
+
if (!parentId)
|
|
37
|
+
continue; // optional ref — not a broken link
|
|
38
|
+
const parent = await readArtifact(projectDir, config, spec.parentType, parentId);
|
|
39
|
+
if (!parent) {
|
|
40
|
+
issues.push({
|
|
41
|
+
childId: childRow.id,
|
|
42
|
+
childType: spec.childType,
|
|
43
|
+
childPath: child.filePath,
|
|
44
|
+
parentField: spec.parentField,
|
|
45
|
+
parentId,
|
|
46
|
+
reason: 'missing-parent',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { ok: issues.length === 0, issues };
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=graph-integrity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-integrity.js","sourceRoot":"","sources":["../../src/services/graph-integrity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAsBpE,MAAM,MAAM,GAAgB;IAC1B,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE;IACnE,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE;IACvE,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE;CACnE,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,UAAkB,EAClB,MAAuB;IAEvB,MAAM,MAAM,GAA0B,EAAE,CAAC;IAEzC,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;QAC1B,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACzE,KAAK,MAAM,QAAQ,IAAI,QAAQ,EAAE,CAAC;YAChC,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;YAClF,IAAI,CAAC,KAAK;gBAAE,SAAS;YACrB,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAuB,CAAC;YACpE,IAAI,CAAC,QAAQ;gBAAE,SAAS,CAAC,mCAAmC;YAE5D,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;YACjF,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,CAAC,IAAI,CAAC;oBACV,OAAO,EAAE,QAAQ,CAAC,EAAE;oBACpB,SAAS,EAAE,IAAI,CAAC,SAAS;oBACzB,SAAS,EAAE,KAAK,CAAC,QAAQ;oBACzB,WAAW,EAAE,IAAI,CAAC,WAAW;oBAC7B,QAAQ;oBACR,MAAM,EAAE,gBAAgB;iBACzB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;AAC7C,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure markdown body/description formatters for Linear issues and projects.
|
|
3
|
+
*
|
|
4
|
+
* Every function here takes local OpenPlanr artifact data and returns the
|
|
5
|
+
* string that goes into the Linear entity's `description` / `title` field.
|
|
6
|
+
* Stateless + side-effect free except for `buildMergedTaskListBody`, which
|
|
7
|
+
* reads task files via the artifact-service to assemble a feature's
|
|
8
|
+
* aggregated checkbox body.
|
|
9
|
+
*/
|
|
10
|
+
import { type ParsedSubtask } from '../../agents/task-parser.js';
|
|
11
|
+
import type { Epic, Feature, OpenPlanrConfig, UserStory } from '../../models/types.js';
|
|
12
|
+
/** Convert an unknown frontmatter value to an optional string at the type boundary. */
|
|
13
|
+
export declare function toOptionalString(v: unknown): string | undefined;
|
|
14
|
+
/** Convert an unknown frontmatter value to an optional array of strings. */
|
|
15
|
+
export declare function toOptionalStringArray(v: unknown): string[] | undefined;
|
|
16
|
+
/** Epic → Linear Project `description` (markdown). Skips whitespace-only sections. */
|
|
17
|
+
export declare function buildEpicProjectDescription(epic: Epic): string;
|
|
18
|
+
/** Feature → Linear issue body (overview + functional requirements bullets). */
|
|
19
|
+
export declare function buildFeatureIssueBody(feature: Feature): string;
|
|
20
|
+
/**
|
|
21
|
+
* User story → Linear sub-issue body.
|
|
22
|
+
*
|
|
23
|
+
* Composes, in order:
|
|
24
|
+
* 1. The "As a / I want / So that" sentence — only when all three fields
|
|
25
|
+
* are present (otherwise rendering with blanks produces visible
|
|
26
|
+
* garbage in Linear).
|
|
27
|
+
* 2. The frontmatter `acceptanceCriteria` prose — if set.
|
|
28
|
+
* 3. The Gherkin scenarios from `<storyId>-gherkin.feature` — if the
|
|
29
|
+
* caller provides them. Stories in the OpenPlanr convention store
|
|
30
|
+
* their real acceptance criteria as Gherkin in a sibling `.feature`
|
|
31
|
+
* file; without this, the Linear issue was empty for every story
|
|
32
|
+
* that followed the convention.
|
|
33
|
+
*/
|
|
34
|
+
export declare function buildStoryIssueBody(story: UserStory, gherkinContent?: string | null): string;
|
|
35
|
+
/** Render parsed task lines to markdown checkboxes (Linear description). */
|
|
36
|
+
export declare function formatTaskCheckboxBody(parsed: ParsedSubtask[]): string;
|
|
37
|
+
/**
|
|
38
|
+
* Build a merged task-list body for a feature — concatenates every task
|
|
39
|
+
* artifact whose `featureId` matches, parses its checkboxes, renders them,
|
|
40
|
+
* and (when multiple files exist) prefixes each section with its task id
|
|
41
|
+
* as an `## h2`. Returns `''` when there's nothing to sync.
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildMergedTaskListBody(projectDir: string, config: OpenPlanrConfig, featureId: string, taskFiles: Array<{
|
|
44
|
+
id: string;
|
|
45
|
+
title: string;
|
|
46
|
+
}>): Promise<string>;
|
|
47
|
+
/**
|
|
48
|
+
* Extract the markdown body of a standalone artifact (QT / BL) for pushing
|
|
49
|
+
* to Linear as an issue description.
|
|
50
|
+
*
|
|
51
|
+
* Strips:
|
|
52
|
+
* - the frontmatter block (YAML between the `---` markers)
|
|
53
|
+
* - the top-level `# <ID>: <title>` heading (Linear shows the title
|
|
54
|
+
* separately, so repeating it in the description is noise)
|
|
55
|
+
*
|
|
56
|
+
* Everything else — prose, sub-headings, checkbox lists — is preserved
|
|
57
|
+
* verbatim. Linear renders standard markdown, so checkboxes stay checkboxes,
|
|
58
|
+
* `## sections` stay sections, links stay clickable.
|
|
59
|
+
*/
|
|
60
|
+
export declare function buildStandaloneArtifactBody(raw: string, id: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* Backlog item → Linear issue body. Priority + tags + description + optional
|
|
63
|
+
* acceptance criteria + notes. Accepts the generic frontmatter record shape
|
|
64
|
+
* because backlog items aren't currently loaded via a typed interface.
|
|
65
|
+
*/
|
|
66
|
+
export declare function buildBacklogItemBody(bl: {
|
|
67
|
+
frontmatter: Record<string, unknown>;
|
|
68
|
+
}): string;
|
|
69
|
+
//# sourceMappingURL=body-formatters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"body-formatters.d.ts","sourceRoot":"","sources":["../../../src/services/linear/body-formatters.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,KAAK,aAAa,EAAqB,MAAM,6BAA6B,CAAC;AACpF,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAGvF,uFAAuF;AACvF,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAE/D;AAED,4EAA4E;AAC5E,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,EAAE,GAAG,SAAS,CAItE;AAED,sFAAsF;AACtF,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAc9D;AAED,gFAAgF;AAChF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAS9D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,SAAS,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAc5F;AAED,4EAA4E;AAC5E,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,CAWtE;AAED;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,eAAe,EACvB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,GAC9C,OAAO,CAAC,MAAM,CAAC,CAqBjB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAgB3E;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAAG,MAAM,CAgBzF"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure markdown body/description formatters for Linear issues and projects.
|
|
3
|
+
*
|
|
4
|
+
* Every function here takes local OpenPlanr artifact data and returns the
|
|
5
|
+
* string that goes into the Linear entity's `description` / `title` field.
|
|
6
|
+
* Stateless + side-effect free except for `buildMergedTaskListBody`, which
|
|
7
|
+
* reads task files via the artifact-service to assemble a feature's
|
|
8
|
+
* aggregated checkbox body.
|
|
9
|
+
*/
|
|
10
|
+
import { parseTaskMarkdown } from '../../agents/task-parser.js';
|
|
11
|
+
import { readArtifact, readArtifactRaw } from '../artifact-service.js';
|
|
12
|
+
/** Convert an unknown frontmatter value to an optional string at the type boundary. */
|
|
13
|
+
export function toOptionalString(v) {
|
|
14
|
+
return typeof v === 'string' ? v : undefined;
|
|
15
|
+
}
|
|
16
|
+
/** Convert an unknown frontmatter value to an optional array of strings. */
|
|
17
|
+
export function toOptionalStringArray(v) {
|
|
18
|
+
if (!Array.isArray(v))
|
|
19
|
+
return undefined;
|
|
20
|
+
const out = v.filter((item) => typeof item === 'string');
|
|
21
|
+
return out.length > 0 ? out : undefined;
|
|
22
|
+
}
|
|
23
|
+
/** Epic → Linear Project `description` (markdown). Skips whitespace-only sections. */
|
|
24
|
+
export function buildEpicProjectDescription(epic) {
|
|
25
|
+
const lines = [];
|
|
26
|
+
const section = (label, value) => {
|
|
27
|
+
const trimmed = value?.trim();
|
|
28
|
+
if (trimmed)
|
|
29
|
+
lines.push(`**${label}**\n\n${trimmed}`);
|
|
30
|
+
};
|
|
31
|
+
section('Business value', epic.businessValue);
|
|
32
|
+
section('Problem', epic.problemStatement);
|
|
33
|
+
section('Solution', epic.solutionOverview);
|
|
34
|
+
section('Success criteria', epic.successCriteria);
|
|
35
|
+
section('Target users', epic.targetUsers);
|
|
36
|
+
section('Risks', epic.risks);
|
|
37
|
+
section('Dependencies', epic.dependencies);
|
|
38
|
+
return lines.join('\n\n');
|
|
39
|
+
}
|
|
40
|
+
/** Feature → Linear issue body (overview + functional requirements bullets). */
|
|
41
|
+
export function buildFeatureIssueBody(feature) {
|
|
42
|
+
const lines = [feature.overview?.trim() || ''];
|
|
43
|
+
if (feature.functionalRequirements?.length) {
|
|
44
|
+
lines.push('**Functional requirements**');
|
|
45
|
+
for (const r of feature.functionalRequirements) {
|
|
46
|
+
lines.push(`- ${r}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return lines.filter(Boolean).join('\n\n');
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* User story → Linear sub-issue body.
|
|
53
|
+
*
|
|
54
|
+
* Composes, in order:
|
|
55
|
+
* 1. The "As a / I want / So that" sentence — only when all three fields
|
|
56
|
+
* are present (otherwise rendering with blanks produces visible
|
|
57
|
+
* garbage in Linear).
|
|
58
|
+
* 2. The frontmatter `acceptanceCriteria` prose — if set.
|
|
59
|
+
* 3. The Gherkin scenarios from `<storyId>-gherkin.feature` — if the
|
|
60
|
+
* caller provides them. Stories in the OpenPlanr convention store
|
|
61
|
+
* their real acceptance criteria as Gherkin in a sibling `.feature`
|
|
62
|
+
* file; without this, the Linear issue was empty for every story
|
|
63
|
+
* that followed the convention.
|
|
64
|
+
*/
|
|
65
|
+
export function buildStoryIssueBody(story, gherkinContent) {
|
|
66
|
+
const role = story.role?.trim();
|
|
67
|
+
const goal = story.goal?.trim();
|
|
68
|
+
const benefit = story.benefit?.trim();
|
|
69
|
+
const ac = story.acceptanceCriteria?.trim();
|
|
70
|
+
const gherkin = gherkinContent?.trim();
|
|
71
|
+
const hasFullUserStoryLine = Boolean(role && goal && benefit);
|
|
72
|
+
const sections = [];
|
|
73
|
+
if (hasFullUserStoryLine) {
|
|
74
|
+
sections.push(`As a **${role}**, I want **${goal}** so that **${benefit}**.`);
|
|
75
|
+
}
|
|
76
|
+
if (ac)
|
|
77
|
+
sections.push(`**Acceptance criteria**\n\n${ac}`);
|
|
78
|
+
if (gherkin)
|
|
79
|
+
sections.push(`**Gherkin scenarios**\n\n\`\`\`gherkin\n${gherkin}\n\`\`\``);
|
|
80
|
+
return sections.join('\n\n');
|
|
81
|
+
}
|
|
82
|
+
/** Render parsed task lines to markdown checkboxes (Linear description). */
|
|
83
|
+
export function formatTaskCheckboxBody(parsed) {
|
|
84
|
+
if (parsed.length === 0)
|
|
85
|
+
return '';
|
|
86
|
+
return parsed
|
|
87
|
+
.map((p) => {
|
|
88
|
+
const mark = p.done ? 'x' : ' ';
|
|
89
|
+
if (p.depth === 0) {
|
|
90
|
+
return `- [${mark}] **${p.id}** ${p.title}`;
|
|
91
|
+
}
|
|
92
|
+
return ` - [${mark}] ${p.id} ${p.title}`;
|
|
93
|
+
})
|
|
94
|
+
.join('\n');
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Build a merged task-list body for a feature — concatenates every task
|
|
98
|
+
* artifact whose `featureId` matches, parses its checkboxes, renders them,
|
|
99
|
+
* and (when multiple files exist) prefixes each section with its task id
|
|
100
|
+
* as an `## h2`. Returns `''` when there's nothing to sync.
|
|
101
|
+
*/
|
|
102
|
+
export async function buildMergedTaskListBody(projectDir, config, featureId, taskFiles) {
|
|
103
|
+
const sections = [];
|
|
104
|
+
const sorted = [...taskFiles].sort((a, b) => a.id.localeCompare(b.id, undefined, { numeric: true }));
|
|
105
|
+
for (const tf of sorted) {
|
|
106
|
+
const raw = await readArtifactRaw(projectDir, config, 'task', tf.id);
|
|
107
|
+
if (!raw)
|
|
108
|
+
continue;
|
|
109
|
+
const data = (await readArtifact(projectDir, config, 'task', tf.id))?.data;
|
|
110
|
+
const fId = toOptionalString(data?.featureId);
|
|
111
|
+
if (fId !== featureId)
|
|
112
|
+
continue;
|
|
113
|
+
const parsed = parseTaskMarkdown(raw);
|
|
114
|
+
if (parsed.length === 0)
|
|
115
|
+
continue;
|
|
116
|
+
const body = formatTaskCheckboxBody(parsed);
|
|
117
|
+
if (taskFiles.length > 1) {
|
|
118
|
+
sections.push(`## ${tf.id}\n\n${body}`);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
sections.push(body);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return sections.join('\n\n');
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Extract the markdown body of a standalone artifact (QT / BL) for pushing
|
|
128
|
+
* to Linear as an issue description.
|
|
129
|
+
*
|
|
130
|
+
* Strips:
|
|
131
|
+
* - the frontmatter block (YAML between the `---` markers)
|
|
132
|
+
* - the top-level `# <ID>: <title>` heading (Linear shows the title
|
|
133
|
+
* separately, so repeating it in the description is noise)
|
|
134
|
+
*
|
|
135
|
+
* Everything else — prose, sub-headings, checkbox lists — is preserved
|
|
136
|
+
* verbatim. Linear renders standard markdown, so checkboxes stay checkboxes,
|
|
137
|
+
* `## sections` stay sections, links stay clickable.
|
|
138
|
+
*/
|
|
139
|
+
export function buildStandaloneArtifactBody(raw, id) {
|
|
140
|
+
// Strip frontmatter if present.
|
|
141
|
+
let body = raw;
|
|
142
|
+
const fmMatch = /^---[^\S\r\n]*\r?\n[\s\S]*?\r?\n---[^\S\r\n]*\r?\n?/.exec(raw);
|
|
143
|
+
if (fmMatch) {
|
|
144
|
+
body = raw.slice(fmMatch[0].length);
|
|
145
|
+
}
|
|
146
|
+
// Drop leading blank lines — markdown files typically have a blank line
|
|
147
|
+
// between the frontmatter's closing `---` and the first `#` heading.
|
|
148
|
+
body = body.replace(/^\s*\r?\n/, '').trimStart();
|
|
149
|
+
// Strip a single top-level `# <ID>:...` or `# <anything>` heading at the
|
|
150
|
+
// start of the body, plus the blank line that typically follows it.
|
|
151
|
+
const escapedId = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
152
|
+
const titleHeadingRegex = new RegExp(`^#\\s+(?:${escapedId}:\\s*)?.*\\r?\\n(?:\\r?\\n)?`);
|
|
153
|
+
body = body.replace(titleHeadingRegex, '');
|
|
154
|
+
return body.trimEnd();
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Backlog item → Linear issue body. Priority + tags + description + optional
|
|
158
|
+
* acceptance criteria + notes. Accepts the generic frontmatter record shape
|
|
159
|
+
* because backlog items aren't currently loaded via a typed interface.
|
|
160
|
+
*/
|
|
161
|
+
export function buildBacklogItemBody(bl) {
|
|
162
|
+
const fm = bl.frontmatter;
|
|
163
|
+
const lines = [];
|
|
164
|
+
const priority = toOptionalString(fm.priority);
|
|
165
|
+
if (priority)
|
|
166
|
+
lines.push(`**Priority:** ${priority}`);
|
|
167
|
+
if (Array.isArray(fm.tags) && fm.tags.length > 0) {
|
|
168
|
+
const tags = fm.tags.filter((t) => typeof t === 'string');
|
|
169
|
+
if (tags.length)
|
|
170
|
+
lines.push(`**Tags:** ${tags.join(', ')}`);
|
|
171
|
+
}
|
|
172
|
+
const description = toOptionalString(fm.description);
|
|
173
|
+
if (description)
|
|
174
|
+
lines.push(description.trim());
|
|
175
|
+
const ac = toOptionalString(fm.acceptanceCriteria);
|
|
176
|
+
if (ac)
|
|
177
|
+
lines.push(`**Acceptance criteria**\n\n${ac.trim()}`);
|
|
178
|
+
const notes = toOptionalString(fm.notes);
|
|
179
|
+
if (notes)
|
|
180
|
+
lines.push(`**Notes**\n\n${notes.trim()}`);
|
|
181
|
+
return lines.join('\n\n');
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=body-formatters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"body-formatters.js","sourceRoot":"","sources":["../../../src/services/linear/body-formatters.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linear integration constants + id-shape validators + input-safety helpers.
|
|
3
|
+
*
|
|
4
|
+
* All field-limit values here are enforced at the SDK-wrapper layer in
|
|
5
|
+
* `src/services/linear-service.ts` so that every caller gets the guard for
|
|
6
|
+
* free. Shape validators (`isLikelyLinear*Id`) fence off stale frontmatter
|
|
7
|
+
* before it reaches the Linear API.
|
|
8
|
+
*/
|
|
9
|
+
/** Credential-service provider key under which the Linear PAT is stored. */
|
|
10
|
+
export declare const LINEAR_CREDENTIAL_KEY: "linear";
|
|
11
|
+
/**
|
|
12
|
+
* Linear's backend enforces per-field length limits on every create/update
|
|
13
|
+
* mutation. Defend at the SDK-wrapper layer so callers don't have to think
|
|
14
|
+
* about them. Names / titles: confirmed or best-known caps; descriptions:
|
|
15
|
+
* conservative floors well under Linear's real ceilings (markdown + HTML
|
|
16
|
+
* are both accepted; real limits are in the tens of thousands).
|
|
17
|
+
*/
|
|
18
|
+
export declare const LINEAR_FIELD_LIMITS: {
|
|
19
|
+
/** ProjectMilestone.name — confirmed 80 by `Argument Validation Error`. */
|
|
20
|
+
readonly milestoneName: 80;
|
|
21
|
+
/** IssueLabel.name — Linear team labels cap ~64 chars. */
|
|
22
|
+
readonly labelName: 64;
|
|
23
|
+
/** Project.name — generous cap. */
|
|
24
|
+
readonly projectName: 256;
|
|
25
|
+
/** Issue.title — Linear issue title cap ~255. */
|
|
26
|
+
readonly issueTitle: 255;
|
|
27
|
+
/** Project.description — conservative floor; real Linear ceiling is higher. */
|
|
28
|
+
readonly projectDescription: 50000;
|
|
29
|
+
/** ProjectMilestone.description. */
|
|
30
|
+
readonly milestoneDescription: 50000;
|
|
31
|
+
/** IssueLabel.description — labels rarely need long descriptions. */
|
|
32
|
+
readonly labelDescription: 500;
|
|
33
|
+
/** Issue.description (markdown body). */
|
|
34
|
+
readonly issueDescription: 65000;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Truncate a string to Linear's character limit for a given field. Logs a
|
|
38
|
+
* warning on truncation so the operator can spot it in the push output.
|
|
39
|
+
*/
|
|
40
|
+
export declare function truncateForLinear(value: string, maxLen: number, fieldLabel: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Non-empty guard for required Linear name/title fields. Fails fast with an
|
|
43
|
+
* actionable message before the API would reject the call.
|
|
44
|
+
*/
|
|
45
|
+
export declare function requireNonEmpty(value: string | null | undefined, fieldLabel: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* Heuristic: Linear workflow state id (uuid) vs human-readable state name.
|
|
48
|
+
* The `/i` flag is intentional — Linear's API canonicalizes UUIDs to
|
|
49
|
+
* lowercase, but defensive acceptance of uppercase hex matches RFC 4122
|
|
50
|
+
* and protects against tools that normalize differently.
|
|
51
|
+
*/
|
|
52
|
+
export declare function isLikelyLinearWorkflowStateId(s: string): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Validate that a value plausibly identifies a Linear issue. Two valid shapes:
|
|
55
|
+
* 1. UUIDv4 (e.g. `9b2f4c3e-...`) — canonical API form
|
|
56
|
+
* 2. Linear identifier (e.g. `ENG-42`) — human-readable, also accepted by `client.issue()`
|
|
57
|
+
* Anything else is treated as stale/corrupted frontmatter and skipped before
|
|
58
|
+
* hitting the API.
|
|
59
|
+
*/
|
|
60
|
+
export declare function isLikelyLinearIssueId(s: string): boolean;
|
|
61
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/services/linear/constants.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,4EAA4E;AAC5E,eAAO,MAAM,qBAAqB,EAAG,QAAiB,CAAC;AAEvD;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB;IAC9B,2EAA2E;;IAE3E,0DAA0D;;IAE1D,mCAAmC;;IAEnC,iDAAiD;;IAEjD,+EAA+E;;IAE/E,oCAAoC;;IAEpC,qEAAqE;;IAErE,yCAAyC;;CAEjC,CAAC;AAEX;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAQ3F;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAQ5F;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAIhE;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAKxD"}
|