@elnora-ai/linear 1.0.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/.claude-plugin/marketplace.json +19 -0
- package/.claude-plugin/plugin.json +19 -0
- package/CHANGELOG.md +29 -0
- package/LICENSE +201 -0
- package/README.md +61 -0
- package/agents/linear-issue-creator.md +45 -0
- package/agents/linear-issue-reviewer.md +44 -0
- package/agents/linear-issue-updater.md +45 -0
- package/agents/linear-url-to-issues.md +51 -0
- package/commands/linear-bulk.md +42 -0
- package/commands/linear-cleanup.md +45 -0
- package/commands/linear-curator-run.md +39 -0
- package/commands/linear-my-issues.md +25 -0
- package/commands/linear-search.md +32 -0
- package/commands/linear-sync.md +54 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +126 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/auth.d.ts +21 -0
- package/dist/client/auth.d.ts.map +1 -0
- package/dist/client/auth.js +74 -0
- package/dist/client/auth.js.map +1 -0
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +3 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/linear-client.d.ts +5 -0
- package/dist/client/linear-client.d.ts.map +1 -0
- package/dist/client/linear-client.js +18 -0
- package/dist/client/linear-client.js.map +1 -0
- package/dist/commands/bulk.d.ts +39 -0
- package/dist/commands/bulk.d.ts.map +1 -0
- package/dist/commands/bulk.js +103 -0
- package/dist/commands/bulk.js.map +1 -0
- package/dist/commands/cleanup.d.ts +39 -0
- package/dist/commands/cleanup.d.ts.map +1 -0
- package/dist/commands/cleanup.js +100 -0
- package/dist/commands/cleanup.js.map +1 -0
- package/dist/commands/curator.d.ts +23 -0
- package/dist/commands/curator.d.ts.map +1 -0
- package/dist/commands/curator.js +66 -0
- package/dist/commands/curator.js.map +1 -0
- package/dist/commands/index.d.ts +7 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +7 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/my-issues.d.ts +7 -0
- package/dist/commands/my-issues.d.ts.map +1 -0
- package/dist/commands/my-issues.js +24 -0
- package/dist/commands/my-issues.js.map +1 -0
- package/dist/commands/search.d.ts +20 -0
- package/dist/commands/search.d.ts.map +1 -0
- package/dist/commands/search.js +54 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/sync.d.ts +87 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +229 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +4 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/loader.d.ts +12 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +140 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/types.d.ts +133 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +13 -0
- package/dist/config/types.js.map +1 -0
- package/dist/output/formatter.d.ts +16 -0
- package/dist/output/formatter.d.ts.map +1 -0
- package/dist/output/formatter.js +34 -0
- package/dist/output/formatter.js.map +1 -0
- package/dist/output/index.d.ts +2 -0
- package/dist/output/index.d.ts.map +1 -0
- package/dist/output/index.js +2 -0
- package/dist/output/index.js.map +1 -0
- package/dist/signals/external-command.d.ts +30 -0
- package/dist/signals/external-command.d.ts.map +1 -0
- package/dist/signals/external-command.js +96 -0
- package/dist/signals/external-command.js.map +1 -0
- package/dist/signals/index.d.ts +4 -0
- package/dist/signals/index.d.ts.map +1 -0
- package/dist/signals/index.js +4 -0
- package/dist/signals/index.js.map +1 -0
- package/dist/signals/registry.d.ts +5 -0
- package/dist/signals/registry.d.ts.map +1 -0
- package/dist/signals/registry.js +29 -0
- package/dist/signals/registry.js.map +1 -0
- package/dist/signals/types.d.ts +25 -0
- package/dist/signals/types.d.ts.map +1 -0
- package/dist/signals/types.js +9 -0
- package/dist/signals/types.js.map +1 -0
- package/package.json +76 -0
- package/references/projects.example.json +26 -0
- package/references/projects.placeholder.json +6 -0
- package/references/repos.example.json +21 -0
- package/references/repos.placeholder.json +6 -0
- package/references/signal-sources.example.json +42 -0
- package/references/signal-sources.placeholder.json +6 -0
- package/references/slack.example.json +10 -0
- package/references/slack.placeholder.json +8 -0
- package/references/teams.example.json +21 -0
- package/references/teams.placeholder.json +6 -0
- package/references/users.example.json +18 -0
- package/references/users.placeholder.json +6 -0
- package/references/workflows.example.json +44 -0
- package/references/workflows.placeholder.json +7 -0
- package/schemas/projects.json +47 -0
- package/schemas/repos.json +41 -0
- package/schemas/signal-sources.json +90 -0
- package/schemas/slack.json +45 -0
- package/schemas/teams.json +43 -0
- package/schemas/users.json +41 -0
- package/schemas/workflows.json +61 -0
- package/skills/linear-workspace/SKILL.md +52 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/output/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/output/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Signal, SignalSourceContext, SignalSourceImpl } from "./types.js";
|
|
2
|
+
export interface ExternalCommandConfig {
|
|
3
|
+
type: "external_command";
|
|
4
|
+
name: string;
|
|
5
|
+
enabled?: boolean;
|
|
6
|
+
command: string;
|
|
7
|
+
parse_as?: "json" | "lines";
|
|
8
|
+
issue_match_field?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare class ExternalCommandSource implements SignalSourceImpl {
|
|
11
|
+
readonly config: ExternalCommandConfig;
|
|
12
|
+
constructor(config: ExternalCommandConfig);
|
|
13
|
+
collect(ctx: SignalSourceContext): Promise<Signal[]>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Split a shell-ish command string into argv.
|
|
17
|
+
*
|
|
18
|
+
* Honors double-quoted runs as a single arg. Does NOT interpolate environment
|
|
19
|
+
* variables, handle single-quotes, or process backslash escapes —
|
|
20
|
+
* intentionally simple. For anything fancier, wrap the command in a shell
|
|
21
|
+
* script and reference that.
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseCommand(input: string): string[];
|
|
24
|
+
export declare function parseOutput(stdout: string, config: {
|
|
25
|
+
name: string;
|
|
26
|
+
type: string;
|
|
27
|
+
parse_as?: "json" | "lines";
|
|
28
|
+
issue_match_field?: string;
|
|
29
|
+
}, now: Date): Signal[];
|
|
30
|
+
//# sourceMappingURL=external-command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"external-command.d.ts","sourceRoot":"","sources":["../../src/signals/external-command.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAIhF,MAAM,WAAW,qBAAqB;IACrC,IAAI,EAAE,kBAAkB,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,qBAAa,qBAAsB,YAAW,gBAAgB;IAC7D,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;gBAE3B,MAAM,EAAE,qBAAqB;IAInC,OAAO,CAAC,GAAG,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAO1D;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAYpD;AAED,wBAAgB,WAAW,CAC1B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAE,EAC/F,GAAG,EAAE,IAAI,GACP,MAAM,EAAE,CA0CV"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// `external_command` signal source.
|
|
2
|
+
//
|
|
3
|
+
// Runs a user-configured shell command and converts the stdout into Signal
|
|
4
|
+
// objects. Two parse modes:
|
|
5
|
+
// - "json" (default): expects either a JSON object or a JSON array of
|
|
6
|
+
// objects. Each object becomes one Signal. If `issue_match_field` is
|
|
7
|
+
// set, that field's string value is lifted onto Signal.issueIdentifier.
|
|
8
|
+
// - "lines": each non-empty line becomes one Signal whose payload is
|
|
9
|
+
// `{ line: "..." }`.
|
|
10
|
+
//
|
|
11
|
+
// The command is run via execFile (NOT a shell), so the command string is
|
|
12
|
+
// split into argv directly with parseCommand — no shell expansion, no glob,
|
|
13
|
+
// no $VAR interpolation. There's a 30s timeout and a 10MB stdout cap.
|
|
14
|
+
//
|
|
15
|
+
// Security: this source runs user-configured arbitrary commands. By design —
|
|
16
|
+
// it's the generic extension point for the curator. Document accordingly.
|
|
17
|
+
import { execFile } from "node:child_process";
|
|
18
|
+
import { promisify } from "node:util";
|
|
19
|
+
const execFileAsync = promisify(execFile);
|
|
20
|
+
export class ExternalCommandSource {
|
|
21
|
+
config;
|
|
22
|
+
constructor(config) {
|
|
23
|
+
this.config = config;
|
|
24
|
+
}
|
|
25
|
+
async collect(ctx) {
|
|
26
|
+
const argv = parseCommand(this.config.command);
|
|
27
|
+
const cmd = argv[0];
|
|
28
|
+
const args = argv.slice(1);
|
|
29
|
+
const { stdout } = await execFileAsync(cmd, args, { timeout: 30_000, maxBuffer: 10_000_000 });
|
|
30
|
+
return parseOutput(stdout, this.config, ctx.now);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Split a shell-ish command string into argv.
|
|
35
|
+
*
|
|
36
|
+
* Honors double-quoted runs as a single arg. Does NOT interpolate environment
|
|
37
|
+
* variables, handle single-quotes, or process backslash escapes —
|
|
38
|
+
* intentionally simple. For anything fancier, wrap the command in a shell
|
|
39
|
+
* script and reference that.
|
|
40
|
+
*/
|
|
41
|
+
export function parseCommand(input) {
|
|
42
|
+
const parts = [];
|
|
43
|
+
const re = /"([^"]*)"|(\S+)/g;
|
|
44
|
+
let match = re.exec(input);
|
|
45
|
+
while (match !== null) {
|
|
46
|
+
parts.push(match[1] !== undefined ? match[1] : match[2]);
|
|
47
|
+
match = re.exec(input);
|
|
48
|
+
}
|
|
49
|
+
if (parts.length === 0) {
|
|
50
|
+
throw new Error("external_command: command string is empty");
|
|
51
|
+
}
|
|
52
|
+
return parts;
|
|
53
|
+
}
|
|
54
|
+
export function parseOutput(stdout, config, now) {
|
|
55
|
+
const mode = config.parse_as ?? "json";
|
|
56
|
+
const receivedAt = now.toISOString();
|
|
57
|
+
if (mode === "lines") {
|
|
58
|
+
return stdout
|
|
59
|
+
.split("\n")
|
|
60
|
+
.map((line) => line.trim())
|
|
61
|
+
.filter((line) => line.length > 0)
|
|
62
|
+
.map((line) => ({
|
|
63
|
+
source: config.name,
|
|
64
|
+
type: config.type,
|
|
65
|
+
payload: { line },
|
|
66
|
+
receivedAt,
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
if (mode === "json") {
|
|
70
|
+
const trimmed = stdout.trim();
|
|
71
|
+
if (trimmed.length === 0)
|
|
72
|
+
return [];
|
|
73
|
+
let parsed;
|
|
74
|
+
try {
|
|
75
|
+
parsed = JSON.parse(trimmed);
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
throw new Error(`external_command "${config.name}": stdout is not valid JSON: ${err.message}`);
|
|
79
|
+
}
|
|
80
|
+
const records = Array.isArray(parsed) ? parsed : [parsed];
|
|
81
|
+
return records.map((rec) => {
|
|
82
|
+
const isObj = typeof rec === "object" && rec !== null && !Array.isArray(rec);
|
|
83
|
+
const payload = isObj ? rec : { value: rec };
|
|
84
|
+
const idCandidate = config.issue_match_field && isObj ? payload[config.issue_match_field] : undefined;
|
|
85
|
+
return {
|
|
86
|
+
source: config.name,
|
|
87
|
+
type: config.type,
|
|
88
|
+
...(typeof idCandidate === "string" ? { issueIdentifier: idCandidate } : {}),
|
|
89
|
+
payload,
|
|
90
|
+
receivedAt,
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
throw new Error(`external_command "${config.name}": unsupported parse_as "${mode}". Use "json" or "lines".`);
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=external-command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"external-command.js","sourceRoot":"","sources":["../../src/signals/external-command.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/signals/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/signals/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { SignalSource as ConfigSignalSource } from "../config/types.js";
|
|
2
|
+
import type { SignalSourceImpl } from "./types.js";
|
|
3
|
+
export declare const IMPLEMENTED_SIGNAL_SOURCE_TYPES: readonly ["external_command"];
|
|
4
|
+
export declare function buildSignalSource(config: ConfigSignalSource): SignalSourceImpl;
|
|
5
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/signals/registry.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,IAAI,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAG7E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,eAAO,MAAM,+BAA+B,+BAAgC,CAAC;AAU7E,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,kBAAkB,GAAG,gBAAgB,CAc9E"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Build a runtime SignalSource from a config entry.
|
|
2
|
+
//
|
|
3
|
+
// Only `external_command` is implemented in this PR. The other config types
|
|
4
|
+
// declared in schemas/signal-sources.json (github_commits, github_pr,
|
|
5
|
+
// slack_messages, linear_issues, mcp_tool) throw a "not yet implemented"
|
|
6
|
+
// error so they surface clearly in curator reports rather than silently
|
|
7
|
+
// being skipped. Adding a new type means: implement a SignalSourceImpl,
|
|
8
|
+
// register it here, and add it to IMPLEMENTED_SIGNAL_SOURCE_TYPES.
|
|
9
|
+
import { ExternalCommandSource } from "./external-command.js";
|
|
10
|
+
export const IMPLEMENTED_SIGNAL_SOURCE_TYPES = ["external_command"];
|
|
11
|
+
const DECLARED_BUT_NOT_IMPLEMENTED = new Set([
|
|
12
|
+
"github_commits",
|
|
13
|
+
"github_pr",
|
|
14
|
+
"slack_messages",
|
|
15
|
+
"linear_issues",
|
|
16
|
+
"mcp_tool",
|
|
17
|
+
]);
|
|
18
|
+
export function buildSignalSource(config) {
|
|
19
|
+
switch (config.type) {
|
|
20
|
+
case "external_command":
|
|
21
|
+
return new ExternalCommandSource(config);
|
|
22
|
+
default:
|
|
23
|
+
if (DECLARED_BUT_NOT_IMPLEMENTED.has(config.type)) {
|
|
24
|
+
throw new Error(`Signal source type "${config.type}" is declared in the schema but not yet implemented in this version. Currently supported: ${IMPLEMENTED_SIGNAL_SOURCE_TYPES.join(", ")}.`);
|
|
25
|
+
}
|
|
26
|
+
throw new Error(`Unknown signal source type "${config.type}". Supported: ${IMPLEMENTED_SIGNAL_SOURCE_TYPES.join(", ")}.`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/signals/registry.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,EAAE;AACF,4EAA4E;AAC5E,sEAAsE;AACtE,yEAAyE;AACzE,wEAAwE;AACxE,wEAAwE;AACxE,mEAAmE;AAInE,OAAO,EAA8B,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAG1F,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAAC,kBAAkB,CAAU,CAAC;AAE7E,MAAM,4BAA4B,GAAwB,IAAI,GAAG,CAAC;IACjE,gBAAgB;IAChB,WAAW;IACX,gBAAgB;IAChB,eAAe;IACf,UAAU;CACV,CAAC,CAAC;AAEH,MAAM,UAAU,iBAAiB,CAAC,MAA0B;IAC3D,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACrB,KAAK,kBAAkB;YACtB,OAAO,IAAI,qBAAqB,CAAC,MAA+B,CAAC,CAAC;QACnE;YACC,IAAI,4BAA4B,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;gBACnD,MAAM,IAAI,KAAK,CACd,uBAAuB,MAAM,CAAC,IAAI,6FAA6F,+BAA+B,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAC5K,CAAC;YACH,CAAC;YACD,MAAM,IAAI,KAAK,CACd,+BAA+B,MAAM,CAAC,IAAI,iBAAiB,+BAA+B,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CACxG,CAAC;IACJ,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface Signal {
|
|
2
|
+
/** Name of the signal source (from config), e.g. "vanta-failing-tests". */
|
|
3
|
+
source: string;
|
|
4
|
+
/** Signal source type, e.g. "external_command". */
|
|
5
|
+
type: string;
|
|
6
|
+
/** If known, the Linear issue this signal is about (e.g. "ENG-101"). */
|
|
7
|
+
issueIdentifier?: string;
|
|
8
|
+
/** Free-form payload — whatever the source produced for this signal. */
|
|
9
|
+
payload: Record<string, unknown>;
|
|
10
|
+
/** ISO timestamp at which the signal was collected. */
|
|
11
|
+
receivedAt: string;
|
|
12
|
+
}
|
|
13
|
+
export interface SignalSourceContext {
|
|
14
|
+
/** Reference "now" for any time-relative logic. Tests inject a fixed value. */
|
|
15
|
+
now: Date;
|
|
16
|
+
}
|
|
17
|
+
export interface SignalSourceImpl {
|
|
18
|
+
readonly config: {
|
|
19
|
+
type: string;
|
|
20
|
+
name: string;
|
|
21
|
+
enabled?: boolean;
|
|
22
|
+
};
|
|
23
|
+
collect(ctx: SignalSourceContext): Promise<Signal[]>;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/signals/types.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,MAAM;IACtB,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IACnC,+EAA+E;IAC/E,GAAG,EAAE,IAAI,CAAC;CACV;AAED,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IACnE,OAAO,CAAC,GAAG,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CACrD"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Signal-source layer shared types.
|
|
2
|
+
//
|
|
3
|
+
// A SignalSource is a runtime implementation that fetches "signals" (things
|
|
4
|
+
// happening outside Linear) which the curator can correlate with Linear
|
|
5
|
+
// issue state. Each source is configured by a JSON entry in
|
|
6
|
+
// references/signal-sources.json; the registry (registry.ts) turns that
|
|
7
|
+
// config into a concrete SignalSource instance.
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/signals/types.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,EAAE;AACF,4EAA4E;AAC5E,wEAAwE;AACxE,4DAA4D;AAC5D,wEAAwE;AACxE,gDAAgD"}
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@elnora-ai/linear",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Linear workspace for Claude Code — search, bulk edit, agents, and a config-driven curator",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"elnora-linear": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/cli.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/",
|
|
12
|
+
"agents/",
|
|
13
|
+
"commands/",
|
|
14
|
+
"skills/",
|
|
15
|
+
"templates/",
|
|
16
|
+
"schemas/",
|
|
17
|
+
"references/*.placeholder.json",
|
|
18
|
+
"references/*.example.json",
|
|
19
|
+
"examples/",
|
|
20
|
+
".claude-plugin/",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE",
|
|
23
|
+
"CHANGELOG.md"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=20"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc",
|
|
33
|
+
"dev": "tsx src/cli.ts",
|
|
34
|
+
"test": "vitest run",
|
|
35
|
+
"test:watch": "vitest",
|
|
36
|
+
"lint": "biome check src/ __tests__/",
|
|
37
|
+
"lint:fix": "biome check --write src/ __tests__/",
|
|
38
|
+
"format": "biome format --write src/ __tests__/",
|
|
39
|
+
"typecheck": "tsc --noEmit"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"linear",
|
|
43
|
+
"linear-app",
|
|
44
|
+
"claude-code",
|
|
45
|
+
"claude-code-plugin",
|
|
46
|
+
"issue-tracker",
|
|
47
|
+
"cli",
|
|
48
|
+
"agent",
|
|
49
|
+
"automation",
|
|
50
|
+
"elnora"
|
|
51
|
+
],
|
|
52
|
+
"author": "Elnora AI <opensource@elnora.ai>",
|
|
53
|
+
"license": "Apache-2.0",
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "https://github.com/Elnora-AI/elnora-linear"
|
|
57
|
+
},
|
|
58
|
+
"bugs": {
|
|
59
|
+
"url": "https://github.com/Elnora-AI/elnora-linear/issues"
|
|
60
|
+
},
|
|
61
|
+
"homepage": "https://github.com/Elnora-AI/elnora-linear",
|
|
62
|
+
"packageManager": "pnpm@10.33.0",
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@biomejs/biome": "^2.4.10",
|
|
65
|
+
"@types/node": "^25.5.0",
|
|
66
|
+
"tsx": "^4.21.0",
|
|
67
|
+
"typescript": "^6.0.2",
|
|
68
|
+
"vitest": "^4.1.2"
|
|
69
|
+
},
|
|
70
|
+
"dependencies": {
|
|
71
|
+
"@linear/sdk": "^84.0.0",
|
|
72
|
+
"ajv": "^8.20.0",
|
|
73
|
+
"ajv-formats": "^3.0.1",
|
|
74
|
+
"commander": "^14.0.3"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../schemas/projects.json",
|
|
3
|
+
"_example": true,
|
|
4
|
+
"projects": [
|
|
5
|
+
{
|
|
6
|
+
"name": "Backend Platform",
|
|
7
|
+
"team": "ENG",
|
|
8
|
+
"lead": "alice",
|
|
9
|
+
"priority": "High",
|
|
10
|
+
"status": "In Progress",
|
|
11
|
+
"description": "Core backend services and shared infrastructure"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "Q3 Compliance Audit",
|
|
15
|
+
"team": "SEC",
|
|
16
|
+
"priority": "Urgent",
|
|
17
|
+
"status": "Planned",
|
|
18
|
+
"sla": "30 days"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "Internal Tooling",
|
|
22
|
+
"team": "OPS",
|
|
23
|
+
"status": "Backlog"
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../schemas/repos.json",
|
|
3
|
+
"_example": true,
|
|
4
|
+
"repos": [
|
|
5
|
+
{
|
|
6
|
+
"name": "backend",
|
|
7
|
+
"org": "example-org",
|
|
8
|
+
"local_path": "~/code/backend",
|
|
9
|
+
"default_branch": "main"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"name": "frontend",
|
|
13
|
+
"org": "example-org"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "infrastructure",
|
|
17
|
+
"org": "example-org",
|
|
18
|
+
"default_branch": "main"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../schemas/signal-sources.json",
|
|
3
|
+
"_example": true,
|
|
4
|
+
"sources": [
|
|
5
|
+
{
|
|
6
|
+
"type": "github_commits",
|
|
7
|
+
"name": "tracked-repo-commits",
|
|
8
|
+
"enabled": true,
|
|
9
|
+
"repos": ["backend", "frontend"],
|
|
10
|
+
"lookback_days": 14
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"type": "github_pr",
|
|
14
|
+
"name": "tracked-repo-prs",
|
|
15
|
+
"enabled": true,
|
|
16
|
+
"repos": ["backend", "frontend"]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"type": "slack_messages",
|
|
20
|
+
"name": "engineering-chatter",
|
|
21
|
+
"enabled": false,
|
|
22
|
+
"channels": ["C000000ENG"],
|
|
23
|
+
"match_patterns": ["closed", "merged", "deployed"]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"type": "external_command",
|
|
27
|
+
"name": "build-status",
|
|
28
|
+
"enabled": false,
|
|
29
|
+
"command": "ci-cli list-failing-builds --json",
|
|
30
|
+
"parse_as": "json",
|
|
31
|
+
"issue_match_field": "linear_issue_id"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"type": "mcp_tool",
|
|
35
|
+
"name": "stripe-payment-failures",
|
|
36
|
+
"enabled": false,
|
|
37
|
+
"server": "stripe-cli",
|
|
38
|
+
"tool": "list_failed_payments",
|
|
39
|
+
"args": { "lookback_hours": 24 }
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../schemas/slack.json",
|
|
3
|
+
"_example": true,
|
|
4
|
+
"channels": [
|
|
5
|
+
{ "id": "C000000ENG", "name": "engineering", "purpose": "default curator destination" },
|
|
6
|
+
{ "id": "C000000GEN", "name": "general" }
|
|
7
|
+
],
|
|
8
|
+
"allowed_channels": ["C000000ENG"],
|
|
9
|
+
"allowed_dm_users": ["alice", "bob"]
|
|
10
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../schemas/teams.json",
|
|
3
|
+
"_example": true,
|
|
4
|
+
"teams": [
|
|
5
|
+
{
|
|
6
|
+
"key": "ENG",
|
|
7
|
+
"name": "Engineering",
|
|
8
|
+
"description": "Core product engineering",
|
|
9
|
+
"default_project": "Backend Platform"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"key": "OPS",
|
|
13
|
+
"name": "Operations",
|
|
14
|
+
"description": "Internal tooling, infra, and on-call"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"key": "SEC",
|
|
18
|
+
"name": "Security"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../schemas/users.json",
|
|
3
|
+
"_example": true,
|
|
4
|
+
"users": [
|
|
5
|
+
{
|
|
6
|
+
"key": "alice",
|
|
7
|
+
"name": "Alice Smith",
|
|
8
|
+
"email": "alice@example.com",
|
|
9
|
+
"linear_user_id": "00000000-0000-0000-0000-000000000001",
|
|
10
|
+
"slack_user_id": "U000000001"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"key": "bob",
|
|
14
|
+
"name": "Bob Johnson",
|
|
15
|
+
"email": "bob@example.com"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../schemas/workflows.json",
|
|
3
|
+
"_example": true,
|
|
4
|
+
"states": [
|
|
5
|
+
{ "name": "Backlog", "type": "backlog" },
|
|
6
|
+
{ "name": "Todo", "type": "unstarted" },
|
|
7
|
+
{ "name": "In Progress", "type": "started" },
|
|
8
|
+
{ "name": "Done", "type": "completed" },
|
|
9
|
+
{ "name": "Canceled", "type": "canceled" }
|
|
10
|
+
],
|
|
11
|
+
"rules": [
|
|
12
|
+
{
|
|
13
|
+
"id": "merged-pr-closes-issue",
|
|
14
|
+
"tier": "high",
|
|
15
|
+
"description": "Auto-close issues whose ID appears in a merged PR title or commit message",
|
|
16
|
+
"when": {
|
|
17
|
+
"signal_type": "github_pr",
|
|
18
|
+
"signal_state": "MERGED",
|
|
19
|
+
"current_issue_state": ["Todo", "In Progress"]
|
|
20
|
+
},
|
|
21
|
+
"action": { "set_state": "Done" }
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": "stale-in-progress",
|
|
25
|
+
"tier": "medium",
|
|
26
|
+
"description": "Ask assignee if a 14-day-inactive In Progress issue is still active",
|
|
27
|
+
"when": {
|
|
28
|
+
"current_issue_state": ["In Progress"],
|
|
29
|
+
"inactive_days_minimum": 14
|
|
30
|
+
},
|
|
31
|
+
"action": { "ask_assignee": "still active?" }
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "stale-todo",
|
|
35
|
+
"tier": "low",
|
|
36
|
+
"description": "Surface 30-day-inactive Todo issues in the next summary report",
|
|
37
|
+
"when": {
|
|
38
|
+
"current_issue_state": ["Todo"],
|
|
39
|
+
"inactive_days_minimum": 30
|
|
40
|
+
},
|
|
41
|
+
"action": { "report_only": true }
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/Elnora-AI/elnora-linear/blob/main/schemas/projects.json",
|
|
4
|
+
"title": "Projects",
|
|
5
|
+
"description": "Linear projects across teams. Populated by `linear-sync projects` from the Linear API.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"$schema": { "type": "string" },
|
|
10
|
+
"_placeholder": { "type": "boolean", "const": true },
|
|
11
|
+
"_example": { "type": "boolean", "const": true },
|
|
12
|
+
"_populated_by": { "type": "string" },
|
|
13
|
+
"projects": {
|
|
14
|
+
"type": "array",
|
|
15
|
+
"items": {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"additionalProperties": false,
|
|
18
|
+
"properties": {
|
|
19
|
+
"name": { "type": "string", "minLength": 1 },
|
|
20
|
+
"team": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "Team key (must exist in teams.json)"
|
|
23
|
+
},
|
|
24
|
+
"lead": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "User key/handle of the project lead (resolves via users.json)"
|
|
27
|
+
},
|
|
28
|
+
"priority": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"enum": ["Urgent", "High", "Normal", "Low"]
|
|
31
|
+
},
|
|
32
|
+
"status": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"enum": ["Planned", "In Progress", "Backlog", "Completed", "Canceled"]
|
|
35
|
+
},
|
|
36
|
+
"description": { "type": "string" },
|
|
37
|
+
"sla": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"description": "Free-form SLA description, e.g. \"30 days\", \"same day\", \"RTO 2hrs\""
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"required": ["name", "team"]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"required": ["projects"]
|
|
47
|
+
}
|