@nonameolsson/nightjar 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +405 -2
- package/dist/account.d.ts +12 -0
- package/dist/account.js +57 -0
- package/dist/account.js.map +1 -0
- package/dist/adapter-module.d.ts +18 -0
- package/dist/adapter-module.js +83 -0
- package/dist/adapter-module.js.map +1 -0
- package/dist/adapters.d.ts +15 -0
- package/dist/adapters.js +160 -0
- package/dist/adapters.js.map +1 -0
- package/dist/args.d.ts +13 -0
- package/dist/args.js +84 -0
- package/dist/args.js.map +1 -0
- package/dist/ask.d.ts +6 -0
- package/dist/ask.js +27 -0
- package/dist/ask.js.map +1 -0
- package/dist/azure-devops/api.d.ts +34 -0
- package/dist/azure-devops/api.js +98 -0
- package/dist/azure-devops/api.js.map +1 -0
- package/dist/azure-devops/code-host.d.ts +36 -0
- package/dist/azure-devops/code-host.js +185 -0
- package/dist/azure-devops/code-host.js.map +1 -0
- package/dist/azure-devops/fake-azure-devops.d.ts +90 -0
- package/dist/azure-devops/fake-azure-devops.js +728 -0
- package/dist/azure-devops/fake-azure-devops.js.map +1 -0
- package/dist/azure-devops/index.d.ts +8 -0
- package/dist/azure-devops/index.js +11 -0
- package/dist/azure-devops/index.js.map +1 -0
- package/dist/azure-devops/sign-in.d.ts +11 -0
- package/dist/azure-devops/sign-in.js +68 -0
- package/dist/azure-devops/sign-in.js.map +1 -0
- package/dist/azure-devops/text.d.ts +1 -0
- package/dist/azure-devops/text.js +41 -0
- package/dist/azure-devops/text.js.map +1 -0
- package/dist/azure-devops/tracker.d.ts +44 -0
- package/dist/azure-devops/tracker.js +333 -0
- package/dist/azure-devops/tracker.js.map +1 -0
- package/dist/build.d.ts +75 -0
- package/dist/build.js +698 -0
- package/dist/build.js.map +1 -0
- package/dist/check.d.ts +8 -0
- package/dist/check.js +25 -0
- package/dist/check.js.map +1 -0
- package/dist/checks.d.ts +45 -0
- package/dist/checks.js +243 -0
- package/dist/checks.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +11 -0
- package/dist/cli.js.map +1 -0
- package/dist/code-host.d.ts +19 -0
- package/dist/code-host.js +2 -0
- package/dist/code-host.js.map +1 -0
- package/dist/config.d.ts +86 -0
- package/dist/config.js +294 -0
- package/dist/config.js.map +1 -0
- package/dist/contract/case.d.ts +4 -0
- package/dist/contract/case.js +2 -0
- package/dist/contract/case.js.map +1 -0
- package/dist/contract/code-host.d.ts +24 -0
- package/dist/contract/code-host.js +108 -0
- package/dist/contract/code-host.js.map +1 -0
- package/dist/contract/command.d.ts +2 -0
- package/dist/contract/command.js +36 -0
- package/dist/contract/command.js.map +1 -0
- package/dist/contract/feature-writer.d.ts +3 -0
- package/dist/contract/feature-writer.js +101 -0
- package/dist/contract/feature-writer.js.map +1 -0
- package/dist/contract/run.d.ts +18 -0
- package/dist/contract/run.js +89 -0
- package/dist/contract/run.js.map +1 -0
- package/dist/contract/tracker.d.ts +30 -0
- package/dist/contract/tracker.js +250 -0
- package/dist/contract/tracker.js.map +1 -0
- package/dist/create.d.ts +9 -0
- package/dist/create.js +112 -0
- package/dist/create.js.map +1 -0
- package/dist/doctor.d.ts +4 -0
- package/dist/doctor.js +42 -0
- package/dist/doctor.js.map +1 -0
- package/dist/fakes/code-host.d.ts +21 -0
- package/dist/fakes/code-host.js +42 -0
- package/dist/fakes/code-host.js.map +1 -0
- package/dist/fakes/harness.d.ts +7 -0
- package/dist/fakes/harness.js +68 -0
- package/dist/fakes/harness.js.map +1 -0
- package/dist/fakes/machine.d.ts +11 -0
- package/dist/fakes/machine.js +101 -0
- package/dist/fakes/machine.js.map +1 -0
- package/dist/fakes/tracker.d.ts +40 -0
- package/dist/fakes/tracker.js +116 -0
- package/dist/fakes/tracker.js.map +1 -0
- package/dist/feature-file.d.ts +12 -0
- package/dist/feature-file.js +84 -0
- package/dist/feature-file.js.map +1 -0
- package/dist/git.d.ts +9 -0
- package/dist/git.js +12 -0
- package/dist/git.js.map +1 -0
- package/dist/github/api.d.ts +37 -0
- package/dist/github/api.js +66 -0
- package/dist/github/api.js.map +1 -0
- package/dist/github/code-host.d.ts +19 -0
- package/dist/github/code-host.js +105 -0
- package/dist/github/code-host.js.map +1 -0
- package/dist/github/fake-gh.d.ts +17 -0
- package/dist/github/fake-gh.js +51 -0
- package/dist/github/fake-gh.js.map +1 -0
- package/dist/github/fake-github.d.ts +58 -0
- package/dist/github/fake-github.js +285 -0
- package/dist/github/fake-github.js.map +1 -0
- package/dist/github/labels.d.ts +3 -0
- package/dist/github/labels.js +44 -0
- package/dist/github/labels.js.map +1 -0
- package/dist/github/tracker.d.ts +28 -0
- package/dist/github/tracker.js +208 -0
- package/dist/github/tracker.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +19 -0
- package/dist/init.js +225 -0
- package/dist/init.js.map +1 -0
- package/dist/ledger.d.ts +13 -0
- package/dist/ledger.js +49 -0
- package/dist/ledger.js.map +1 -0
- package/dist/main.d.ts +11 -0
- package/dist/main.js +215 -0
- package/dist/main.js.map +1 -0
- package/dist/person-config.d.ts +9 -0
- package/dist/person-config.js +46 -0
- package/dist/person-config.js.map +1 -0
- package/dist/plan.d.ts +9 -0
- package/dist/plan.js +27 -0
- package/dist/plan.js.map +1 -0
- package/dist/ports.d.ts +16 -0
- package/dist/ports.js +104 -0
- package/dist/ports.js.map +1 -0
- package/dist/prompt.d.ts +166 -0
- package/dist/prompt.js +217 -0
- package/dist/prompt.js.map +1 -0
- package/dist/queue.d.ts +20 -0
- package/dist/queue.js +94 -0
- package/dist/queue.js.map +1 -0
- package/dist/say.d.ts +2 -0
- package/dist/say.js +4 -0
- package/dist/say.js.map +1 -0
- package/dist/serve.d.ts +10 -0
- package/dist/serve.js +85 -0
- package/dist/serve.js.map +1 -0
- package/dist/session.d.ts +39 -0
- package/dist/session.js +144 -0
- package/dist/session.js.map +1 -0
- package/dist/settings.d.ts +10 -0
- package/dist/settings.js +92 -0
- package/dist/settings.js.map +1 -0
- package/dist/signals.d.ts +5 -0
- package/dist/signals.js +13 -0
- package/dist/signals.js.map +1 -0
- package/dist/skills.d.ts +4 -0
- package/dist/skills.js +51 -0
- package/dist/skills.js.map +1 -0
- package/dist/status-shape.d.ts +53 -0
- package/dist/status-shape.js +3 -0
- package/dist/status-shape.js.map +1 -0
- package/dist/status.d.ts +23 -0
- package/dist/status.js +56 -0
- package/dist/status.js.map +1 -0
- package/dist/tracker.d.ts +56 -0
- package/dist/tracker.js +12 -0
- package/dist/tracker.js.map +1 -0
- package/dist/workflow.d.ts +33 -0
- package/dist/workflow.js +53 -0
- package/dist/workflow.js.map +1 -0
- package/dist/worktree.d.ts +44 -0
- package/dist/worktree.js +193 -0
- package/dist/worktree.js.map +1 -0
- package/docs/ticket-contract.md +95 -0
- package/package.json +74 -5
- package/plugin/nightjar-status/.claude-plugin/plugin.json +5 -0
- package/plugin/nightjar-status/hooks/format.ts +48 -0
- package/plugin/nightjar-status/hooks/hooks.json +1 -0
- package/plugin/nightjar-status/hooks/register.ts +51 -0
- package/schema/factory.schema.json +437 -0
- package/skills/nightjar-feature/SKILL.md +73 -0
- package/src/status-shape.ts +44 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Env } from './git.js';
|
|
2
|
+
export interface SessionRequest {
|
|
3
|
+
readonly cwd: string;
|
|
4
|
+
readonly prompt: string;
|
|
5
|
+
readonly schema: object;
|
|
6
|
+
readonly tools: readonly string[];
|
|
7
|
+
readonly settings: object;
|
|
8
|
+
readonly budgetUsd: number;
|
|
9
|
+
readonly minutes: number;
|
|
10
|
+
readonly sessionId: string;
|
|
11
|
+
readonly resume?: string;
|
|
12
|
+
readonly model?: string;
|
|
13
|
+
readonly effort?: string;
|
|
14
|
+
readonly log: string;
|
|
15
|
+
readonly allowApiKey: boolean;
|
|
16
|
+
readonly env: Env;
|
|
17
|
+
readonly signal?: AbortSignal;
|
|
18
|
+
}
|
|
19
|
+
export type Settled = {
|
|
20
|
+
readonly kind: 'done';
|
|
21
|
+
readonly costUsd: number;
|
|
22
|
+
readonly output: unknown;
|
|
23
|
+
} | {
|
|
24
|
+
readonly kind: 'stopped';
|
|
25
|
+
readonly costUsd: number;
|
|
26
|
+
readonly why: string;
|
|
27
|
+
} | {
|
|
28
|
+
readonly kind: 'api-key';
|
|
29
|
+
} | {
|
|
30
|
+
readonly kind: 'rate-limited';
|
|
31
|
+
readonly resetsAt: number | null;
|
|
32
|
+
};
|
|
33
|
+
export type SessionResult = Settled & {
|
|
34
|
+
readonly sessionId: string;
|
|
35
|
+
};
|
|
36
|
+
export type Session = (req: SessionRequest) => Promise<SessionResult>;
|
|
37
|
+
export declare function sessionArgs(req: SessionRequest): string[];
|
|
38
|
+
export declare function settle(line: unknown, allowApiKey: boolean): Settled | null;
|
|
39
|
+
export declare function runSession(req: SessionRequest, command?: string): Promise<SessionResult>;
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// One headless `claude -p` session, run restricted on Nightjar's own settings; every line it prints is kept in its log.
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { createWriteStream, mkdirSync } from 'node:fs';
|
|
4
|
+
import { dirname } from 'node:path';
|
|
5
|
+
import { createInterface } from 'node:readline';
|
|
6
|
+
export function sessionArgs(req) {
|
|
7
|
+
const args = [
|
|
8
|
+
'-p',
|
|
9
|
+
req.prompt,
|
|
10
|
+
'--restricted',
|
|
11
|
+
'--strict-mcp-config',
|
|
12
|
+
'--output-format',
|
|
13
|
+
'stream-json',
|
|
14
|
+
'--verbose',
|
|
15
|
+
'--tools',
|
|
16
|
+
req.tools.join(','),
|
|
17
|
+
'--settings',
|
|
18
|
+
JSON.stringify(req.settings),
|
|
19
|
+
'--json-schema',
|
|
20
|
+
JSON.stringify(req.schema),
|
|
21
|
+
'--max-budget-usd',
|
|
22
|
+
req.budgetUsd.toFixed(2)
|
|
23
|
+
];
|
|
24
|
+
if (req.resume)
|
|
25
|
+
args.push('--resume', req.resume);
|
|
26
|
+
else
|
|
27
|
+
args.push('--session-id', req.sessionId);
|
|
28
|
+
if (req.model)
|
|
29
|
+
args.push('--model', req.model);
|
|
30
|
+
if (req.effort)
|
|
31
|
+
args.push('--effort', req.effort);
|
|
32
|
+
return args;
|
|
33
|
+
}
|
|
34
|
+
const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
35
|
+
// What one stream-json line settles, or null while the session goes on.
|
|
36
|
+
export function settle(line, allowApiKey) {
|
|
37
|
+
if (!isObject(line))
|
|
38
|
+
return null;
|
|
39
|
+
// a key in use bills per token, beside the person's plan, unless --use-api-key chose it
|
|
40
|
+
if (line.type === 'system' && line.subtype === 'init') {
|
|
41
|
+
return !allowApiKey && line.apiKeySource !== 'none' ? { kind: 'api-key' } : null;
|
|
42
|
+
}
|
|
43
|
+
if (line.type === 'rate_limit_event') {
|
|
44
|
+
const info = line.rate_limit_info;
|
|
45
|
+
if (!isObject(info) || info.status !== 'rejected')
|
|
46
|
+
return null;
|
|
47
|
+
return {
|
|
48
|
+
kind: 'rate-limited',
|
|
49
|
+
resetsAt: typeof info.resetsAt === 'number' ? info.resetsAt : null
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (line.type !== 'result')
|
|
53
|
+
return null;
|
|
54
|
+
const costUsd = typeof line.total_cost_usd === 'number' ? line.total_cost_usd : 0;
|
|
55
|
+
if (line.subtype === 'error_max_budget_usd') {
|
|
56
|
+
return { kind: 'stopped', costUsd, why: 'it reached its budget' };
|
|
57
|
+
}
|
|
58
|
+
if (line.is_error === true) {
|
|
59
|
+
return { kind: 'stopped', costUsd, why: `it ended with ${String(line.subtype)}` };
|
|
60
|
+
}
|
|
61
|
+
if (!isObject(line.structured_output)) {
|
|
62
|
+
return { kind: 'stopped', costUsd, why: 'it gave no structured answer' };
|
|
63
|
+
}
|
|
64
|
+
return { kind: 'done', costUsd, output: line.structured_output };
|
|
65
|
+
}
|
|
66
|
+
const TAIL = 2000;
|
|
67
|
+
// `command` is the seam a spec swaps for a stand-in.
|
|
68
|
+
export function runSession(req, command = 'claude') {
|
|
69
|
+
mkdirSync(dirname(req.log), { recursive: true });
|
|
70
|
+
const log = createWriteStream(req.log, { flags: 'a' });
|
|
71
|
+
return new Promise((done) => {
|
|
72
|
+
const child = spawn(command, sessionArgs(req), {
|
|
73
|
+
cwd: req.cwd,
|
|
74
|
+
env: req.env,
|
|
75
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
76
|
+
// its own process group, so a stop also ends every command it started
|
|
77
|
+
detached: true
|
|
78
|
+
});
|
|
79
|
+
const stop = () => {
|
|
80
|
+
try {
|
|
81
|
+
if (child.pid !== undefined)
|
|
82
|
+
process.kill(-child.pid, 'SIGTERM');
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// the group has already ended
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
let stderr = '';
|
|
89
|
+
let settled = null;
|
|
90
|
+
let finished = false;
|
|
91
|
+
const end = (outcome) => {
|
|
92
|
+
if (finished)
|
|
93
|
+
return;
|
|
94
|
+
finished = true;
|
|
95
|
+
clearTimeout(timer);
|
|
96
|
+
req.signal?.removeEventListener('abort', abort);
|
|
97
|
+
stop();
|
|
98
|
+
const result = { ...outcome, sessionId: req.resume ?? req.sessionId };
|
|
99
|
+
// a log that failed is already closed, and would never call back
|
|
100
|
+
if (log.errored)
|
|
101
|
+
done(result);
|
|
102
|
+
else
|
|
103
|
+
log.end(() => done(result));
|
|
104
|
+
};
|
|
105
|
+
const timer = setTimeout(() => end({ kind: 'stopped', costUsd: 0, why: `it ran past ${String(req.minutes)} minutes` }), req.minutes * 60_000);
|
|
106
|
+
const abort = () => end({ kind: 'stopped', costUsd: 0, why: 'stopped by a person' });
|
|
107
|
+
if (req.signal?.aborted)
|
|
108
|
+
abort();
|
|
109
|
+
req.signal?.addEventListener('abort', abort, { once: true });
|
|
110
|
+
child.stderr.on('data', (chunk) => {
|
|
111
|
+
stderr = (stderr + chunk.toString()).slice(-TAIL);
|
|
112
|
+
});
|
|
113
|
+
createInterface({ input: child.stdout }).on('line', (text) => {
|
|
114
|
+
log.write(`${text}\n`);
|
|
115
|
+
let line;
|
|
116
|
+
try {
|
|
117
|
+
line = JSON.parse(text);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const now = settle(line, req.allowApiKey);
|
|
123
|
+
if (now === null)
|
|
124
|
+
return;
|
|
125
|
+
// the result is not always the last line, so a session that settled is let finish
|
|
126
|
+
if (now.kind === 'done' || now.kind === 'stopped')
|
|
127
|
+
settled = now;
|
|
128
|
+
else
|
|
129
|
+
end(now);
|
|
130
|
+
});
|
|
131
|
+
// a log that cannot be written stops the session; thrown, it would leave the Feature marked as worked on
|
|
132
|
+
log.on('error', (fault) => end({ kind: 'stopped', costUsd: 0, why: `its log could not be written: ${fault.message}` }));
|
|
133
|
+
child.on('error', (fault) => end({ kind: 'stopped', costUsd: 0, why: `claude could not start: ${fault.message}` }));
|
|
134
|
+
child.on('close', (code) => {
|
|
135
|
+
const last = stderr.trim().split('\n').pop() ?? '';
|
|
136
|
+
end(settled ?? {
|
|
137
|
+
kind: 'stopped',
|
|
138
|
+
costUsd: 0,
|
|
139
|
+
why: `claude exited with code ${String(code)} and no result${last ? `: ${last}` : ''}`
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const BUILD_TOOLS: string[];
|
|
2
|
+
export declare const REVIEW_TOOLS: string[];
|
|
3
|
+
export interface StepExtras {
|
|
4
|
+
readonly allowedCommands?: readonly string[];
|
|
5
|
+
readonly allowedTools?: readonly string[];
|
|
6
|
+
readonly localNetwork?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare const stepTools: (extras: StepExtras) => string[];
|
|
9
|
+
export declare function buildSettings(allowed: readonly string[], extras?: StepExtras): object;
|
|
10
|
+
export declare function reviewSettings(): object;
|
package/dist/settings.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// --restricted keeps the person's own settings out, and dontAsk refuses what is not allowed here: nobody is there to approve it.
|
|
2
|
+
export const BUILD_TOOLS = [
|
|
3
|
+
'Read',
|
|
4
|
+
'Edit',
|
|
5
|
+
'Write',
|
|
6
|
+
'Glob',
|
|
7
|
+
'Grep',
|
|
8
|
+
'Bash',
|
|
9
|
+
'Skill',
|
|
10
|
+
'Agent',
|
|
11
|
+
'TodoWrite'
|
|
12
|
+
];
|
|
13
|
+
// The reviewer only reads: no Edit, no Write, and Bash only for git's read commands.
|
|
14
|
+
export const REVIEW_TOOLS = ['Read', 'Glob', 'Grep', 'Bash'];
|
|
15
|
+
// Deny wins over any allow in Claude Code, so no config can loosen these.
|
|
16
|
+
const DENY = [
|
|
17
|
+
'Bash(git commit *)',
|
|
18
|
+
'Bash(git push *)',
|
|
19
|
+
'Bash(git pull *)',
|
|
20
|
+
'Bash(git fetch *)',
|
|
21
|
+
'Bash(git clone *)',
|
|
22
|
+
'Bash(git reset *)',
|
|
23
|
+
'Bash(git checkout *)',
|
|
24
|
+
'Bash(git switch *)',
|
|
25
|
+
'Bash(git rebase *)',
|
|
26
|
+
'Bash(git merge *)',
|
|
27
|
+
'Bash(git stash *)',
|
|
28
|
+
'Bash(git clean *)',
|
|
29
|
+
'Bash(git branch *)',
|
|
30
|
+
'Bash(git worktree *)',
|
|
31
|
+
'Bash(git remote *)',
|
|
32
|
+
'Bash(git config *)',
|
|
33
|
+
'Bash(gh *)',
|
|
34
|
+
'Bash(curl *)',
|
|
35
|
+
'Bash(wget *)',
|
|
36
|
+
'Bash(ssh *)',
|
|
37
|
+
'Bash(scp *)',
|
|
38
|
+
'Bash(nc *)',
|
|
39
|
+
'Read(~/.ssh/**)',
|
|
40
|
+
'Read(~/.config/**)',
|
|
41
|
+
'Read(~/.aws/**)',
|
|
42
|
+
'WebFetch',
|
|
43
|
+
'WebSearch'
|
|
44
|
+
];
|
|
45
|
+
const READ_GIT = [
|
|
46
|
+
'Bash(git status)',
|
|
47
|
+
'Bash(git status *)',
|
|
48
|
+
'Bash(git diff)',
|
|
49
|
+
'Bash(git diff *)',
|
|
50
|
+
'Bash(git log *)',
|
|
51
|
+
'Bash(git show *)'
|
|
52
|
+
];
|
|
53
|
+
// The sandbox keeps a Step's commands to localhost; it auto-allows nothing, so the allow list still decides.
|
|
54
|
+
const LOCAL_ONLY = {
|
|
55
|
+
enabled: true,
|
|
56
|
+
autoAllowBashIfSandboxed: false,
|
|
57
|
+
network: { allowedDomains: ['localhost', '127.0.0.1'], allowLocalBinding: true }
|
|
58
|
+
};
|
|
59
|
+
export const stepTools = (extras) => [...BUILD_TOOLS, ...(extras.allowedTools ?? [])];
|
|
60
|
+
// `allowed` are factory.json's allowedCommands, as Bash permission patterns.
|
|
61
|
+
export function buildSettings(allowed, extras = {}) {
|
|
62
|
+
return {
|
|
63
|
+
permissions: {
|
|
64
|
+
defaultMode: 'dontAsk',
|
|
65
|
+
allow: [
|
|
66
|
+
'Read',
|
|
67
|
+
'Edit',
|
|
68
|
+
'Write',
|
|
69
|
+
'Glob',
|
|
70
|
+
'Grep',
|
|
71
|
+
'Skill',
|
|
72
|
+
'Agent',
|
|
73
|
+
'TodoWrite',
|
|
74
|
+
...(extras.allowedTools ?? []),
|
|
75
|
+
...[...allowed, ...(extras.allowedCommands ?? [])].map((command) => `Bash(${command})`),
|
|
76
|
+
...READ_GIT
|
|
77
|
+
],
|
|
78
|
+
deny: DENY
|
|
79
|
+
},
|
|
80
|
+
...(extras.localNetwork === true ? { sandbox: LOCAL_ONLY } : {})
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export function reviewSettings() {
|
|
84
|
+
return {
|
|
85
|
+
permissions: {
|
|
86
|
+
defaultMode: 'dontAsk',
|
|
87
|
+
allow: ['Read', 'Glob', 'Grep', ...READ_GIT],
|
|
88
|
+
deny: [...DENY, 'Edit', 'Write']
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=settings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA,iIAAiI;AAEjI,MAAM,CAAC,MAAM,WAAW,GAAG;IAC1B,MAAM;IACN,MAAM;IACN,OAAO;IACP,MAAM;IACN,MAAM;IACN,MAAM;IACN,OAAO;IACP,OAAO;IACP,WAAW;CACX,CAAC;AAEF,qFAAqF;AACrF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAE7D,0EAA0E;AAC1E,MAAM,IAAI,GAAG;IACZ,oBAAoB;IACpB,kBAAkB;IAClB,kBAAkB;IAClB,mBAAmB;IACnB,mBAAmB;IACnB,mBAAmB;IACnB,sBAAsB;IACtB,oBAAoB;IACpB,oBAAoB;IACpB,mBAAmB;IACnB,mBAAmB;IACnB,mBAAmB;IACnB,oBAAoB;IACpB,sBAAsB;IACtB,oBAAoB;IACpB,oBAAoB;IACpB,YAAY;IACZ,cAAc;IACd,cAAc;IACd,aAAa;IACb,aAAa;IACb,YAAY;IACZ,iBAAiB;IACjB,oBAAoB;IACpB,iBAAiB;IACjB,UAAU;IACV,WAAW;CACX,CAAC;AAEF,MAAM,QAAQ,GAAG;IAChB,kBAAkB;IAClB,oBAAoB;IACpB,gBAAgB;IAChB,kBAAkB;IAClB,iBAAiB;IACjB,kBAAkB;CAClB,CAAC;AASF,6GAA6G;AAC7G,MAAM,UAAU,GAAG;IAClB,OAAO,EAAE,IAAI;IACb,wBAAwB,EAAE,KAAK;IAC/B,OAAO,EAAE,EAAE,cAAc,EAAE,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,iBAAiB,EAAE,IAAI,EAAE;CAChF,CAAC;AAEF,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,MAAkB,EAAE,EAAE,CAAC,CAAC,GAAG,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,CAAC;AAElG,6EAA6E;AAC7E,MAAM,UAAU,aAAa,CAAC,OAA0B,EAAE,SAAqB,EAAE;IAChF,OAAO;QACN,WAAW,EAAE;YACZ,WAAW,EAAE,SAAS;YACtB,KAAK,EAAE;gBACN,MAAM;gBACN,MAAM;gBACN,OAAO;gBACP,MAAM;gBACN,MAAM;gBACN,OAAO;gBACP,OAAO;gBACP,WAAW;gBACX,GAAG,CAAC,MAAM,CAAC,YAAY,IAAI,EAAE,CAAC;gBAC9B,GAAG,CAAC,GAAG,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,QAAQ,OAAO,GAAG,CAAC;gBACvF,GAAG,QAAQ;aACX;YACD,IAAI,EAAE,IAAI;SACV;QACD,GAAG,CAAC,MAAM,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAChE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc;IAC7B,OAAO;QACN,WAAW,EAAE;YACZ,WAAW,EAAE,SAAS;YACtB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC;YAC5C,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC;SAChC;KACD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const SIGNALS: readonly ["go", "ready", "working", "needsInfo", "human"];
|
|
2
|
+
export type Signal = (typeof SIGNALS)[number];
|
|
3
|
+
export type SignalNames = Readonly<Record<Signal, string>>;
|
|
4
|
+
export declare const DEFAULT_SIGNAL_NAMES: SignalNames;
|
|
5
|
+
export declare const RESERVED_NAMES: readonly ["needs-triage", "wontfix"];
|
package/dist/signals.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// The Signals (GLOSSARY.md): shown under names a Tracker configures, never as tracker states (docs/adr/0002).
|
|
2
|
+
export const SIGNALS = ['go', 'ready', 'working', 'needsInfo', 'human'];
|
|
3
|
+
// The names factory.json leaves unset: the Go has a mark of its own; the rest match the triage roles.
|
|
4
|
+
export const DEFAULT_SIGNAL_NAMES = {
|
|
5
|
+
go: 'factory-go',
|
|
6
|
+
ready: 'ready-for-agent',
|
|
7
|
+
working: 'agent-working',
|
|
8
|
+
needsInfo: 'needs-info',
|
|
9
|
+
human: 'ready-for-human'
|
|
10
|
+
};
|
|
11
|
+
// Triage roles of someone else's process: Nightjar never reads or sets them, so no Signal may take one.
|
|
12
|
+
export const RESERVED_NAMES = ['needs-triage', 'wontfix'];
|
|
13
|
+
//# sourceMappingURL=signals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signals.js","sourceRoot":"","sources":["../src/signals.ts"],"names":[],"mappings":"AAAA,8GAA8G;AAC9G,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO,CAAU,CAAC;AAMjF,sGAAsG;AACtG,MAAM,CAAC,MAAM,oBAAoB,GAAgB;IAChD,EAAE,EAAE,YAAY;IAChB,KAAK,EAAE,iBAAiB;IACxB,OAAO,EAAE,eAAe;IACxB,SAAS,EAAE,YAAY;IACvB,KAAK,EAAE,iBAAiB;CACxB,CAAC;AAEF,wGAAwG;AACxG,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,cAAc,EAAE,SAAS,CAAU,CAAC"}
|
package/dist/skills.d.ts
ADDED
package/dist/skills.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// The skills a headless Claude session loads, read off the init line it prints before it asks the model anything.
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { createInterface } from 'node:readline';
|
|
4
|
+
const WAIT_MS = 30_000;
|
|
5
|
+
const strings = (value) => Array.isArray(value) ? value.filter((v) => typeof v === 'string') : [];
|
|
6
|
+
// The session is started as a build starts one and killed at its init line, so it costs nothing.
|
|
7
|
+
export const loadedSkills = (cwd, env) => new Promise((done) => {
|
|
8
|
+
const child = spawn('claude', [
|
|
9
|
+
'-p',
|
|
10
|
+
'Reply with ok.',
|
|
11
|
+
'--restricted',
|
|
12
|
+
'--strict-mcp-config',
|
|
13
|
+
'--output-format',
|
|
14
|
+
'stream-json',
|
|
15
|
+
'--verbose',
|
|
16
|
+
'--tools',
|
|
17
|
+
'Skill'
|
|
18
|
+
], { cwd, env, stdio: ['ignore', 'pipe', 'ignore'], detached: true });
|
|
19
|
+
let finished = false;
|
|
20
|
+
const end = (skills) => {
|
|
21
|
+
if (finished)
|
|
22
|
+
return;
|
|
23
|
+
finished = true;
|
|
24
|
+
clearTimeout(timer);
|
|
25
|
+
try {
|
|
26
|
+
if (child.pid !== undefined)
|
|
27
|
+
process.kill(-child.pid, 'SIGTERM');
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// the group has already ended
|
|
31
|
+
}
|
|
32
|
+
done(skills);
|
|
33
|
+
};
|
|
34
|
+
const timer = setTimeout(() => end(null), WAIT_MS);
|
|
35
|
+
createInterface({ input: child.stdout }).on('line', (text) => {
|
|
36
|
+
try {
|
|
37
|
+
const line = JSON.parse(text);
|
|
38
|
+
if (line.type === 'system' && line.subtype === 'init') {
|
|
39
|
+
end([...new Set([...strings(line.skills), ...strings(line.slash_commands)])]);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// not a JSON line
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
child.on('error', () => end(null));
|
|
47
|
+
child.on('close', () => end(null));
|
|
48
|
+
});
|
|
49
|
+
// Whether `name` is among the loaded skills, bare or under a plugin's prefix.
|
|
50
|
+
export const isLoaded = (loaded, name) => loaded.some((l) => l === name || l.endsWith(`:${name}`));
|
|
51
|
+
//# sourceMappingURL=skills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.js","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"AAAA,kHAAkH;AAClH,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAKhD,MAAM,OAAO,GAAG,MAAM,CAAC;AAEvB,MAAM,OAAO,GAAG,CAAC,KAAc,EAAY,EAAE,CAC5C,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAErF,iGAAiG;AACjG,MAAM,CAAC,MAAM,YAAY,GAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CACtD,IAAI,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;IACpB,MAAM,KAAK,GAAG,KAAK,CAClB,QAAQ,EACR;QACC,IAAI;QACJ,gBAAgB;QAChB,cAAc;QACd,qBAAqB;QACrB,iBAAiB;QACjB,aAAa;QACb,WAAW;QACX,SAAS;QACT,OAAO;KACP,EACD,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CACjE,CAAC;IACF,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,MAAM,GAAG,GAAG,CAAC,MAAuB,EAAE,EAAE;QACvC,IAAI,QAAQ;YAAE,OAAO;QACrB,QAAQ,GAAG,IAAI,CAAC;QAChB,YAAY,CAAC,KAAK,CAAC,CAAC;QACpB,IAAI,CAAC;YACJ,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS;gBAAE,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAClE,CAAC;QAAC,MAAM,CAAC;YACR,8BAA8B;QAC/B,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,CAAC;IACd,CAAC,CAAC;IACF,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;IACnD,eAAe,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;QAC5D,IAAI,CAAC;YACJ,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAA4B,CAAC;YACzD,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,OAAO,KAAK,MAAM,EAAE,CAAC;gBACvD,GAAG,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC/E,CAAC;QACF,CAAC;QAAC,MAAM,CAAC;YACR,kBAAkB;QACnB,CAAC;IACF,CAAC,CAAC,CAAC;IACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IACnC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;AACpC,CAAC,CAAC,CAAC;AAEJ,8EAA8E;AAC9E,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,MAAyB,EAAE,IAAY,EAAE,EAAE,CACnE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export type Doing = {
|
|
2
|
+
readonly kind: 'idle';
|
|
3
|
+
readonly nextPollAt: string;
|
|
4
|
+
} | {
|
|
5
|
+
readonly kind: 'building';
|
|
6
|
+
readonly feature: string;
|
|
7
|
+
readonly ticket: string | null;
|
|
8
|
+
readonly step: 'setup' | 'build' | 'fix' | 'review' | 'step';
|
|
9
|
+
readonly round: number | null;
|
|
10
|
+
readonly rounds: number | null;
|
|
11
|
+
readonly session: string | null;
|
|
12
|
+
readonly startedAt: string;
|
|
13
|
+
} | {
|
|
14
|
+
readonly kind: 'paused';
|
|
15
|
+
readonly why: string;
|
|
16
|
+
readonly until: string | null;
|
|
17
|
+
} | {
|
|
18
|
+
readonly kind: 'off';
|
|
19
|
+
};
|
|
20
|
+
export type Happened = {
|
|
21
|
+
readonly kind: 'built';
|
|
22
|
+
readonly feature: string;
|
|
23
|
+
readonly ticket: string;
|
|
24
|
+
} | {
|
|
25
|
+
readonly kind: 'needs-info';
|
|
26
|
+
readonly feature: string;
|
|
27
|
+
readonly ticket: string | null;
|
|
28
|
+
readonly why: string;
|
|
29
|
+
} | {
|
|
30
|
+
readonly kind: 'failed';
|
|
31
|
+
readonly feature: string;
|
|
32
|
+
readonly why: string;
|
|
33
|
+
} | {
|
|
34
|
+
readonly kind: 'pull';
|
|
35
|
+
readonly feature: string;
|
|
36
|
+
readonly url: string;
|
|
37
|
+
} | {
|
|
38
|
+
readonly kind: 'stale';
|
|
39
|
+
readonly commit: string;
|
|
40
|
+
};
|
|
41
|
+
export type Event = Happened & {
|
|
42
|
+
readonly seq: number;
|
|
43
|
+
readonly at: string;
|
|
44
|
+
};
|
|
45
|
+
export interface Status {
|
|
46
|
+
readonly at: string;
|
|
47
|
+
readonly pid: number;
|
|
48
|
+
readonly spentTodayUsd: number;
|
|
49
|
+
readonly dailyUsd: number;
|
|
50
|
+
readonly doing: Doing;
|
|
51
|
+
readonly recent: Event[];
|
|
52
|
+
readonly codeMoved?: string | null;
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status-shape.js","sourceRoot":"","sources":["../src/status-shape.ts"],"names":[],"mappings":"AAAA,0HAA0H"}
|
package/dist/status.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Doing, Happened, Status } from './status-shape.js';
|
|
2
|
+
export type { Doing, Event, Happened, Status } from './status-shape.js';
|
|
3
|
+
export declare const statusPath: (root: string) => string;
|
|
4
|
+
export interface StatusReport {
|
|
5
|
+
readonly doing: (doing: Doing) => void;
|
|
6
|
+
readonly happened: (event: Happened) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function readStatus(path: string): Status | null;
|
|
9
|
+
export declare class StatusFile implements StatusReport {
|
|
10
|
+
private readonly o;
|
|
11
|
+
private now;
|
|
12
|
+
private recent;
|
|
13
|
+
constructor(o: {
|
|
14
|
+
readonly path: string;
|
|
15
|
+
readonly ledger: string;
|
|
16
|
+
readonly now: () => Date;
|
|
17
|
+
readonly pid: number;
|
|
18
|
+
readonly dailyUsd: number;
|
|
19
|
+
});
|
|
20
|
+
readonly doing: (doing: Doing) => void;
|
|
21
|
+
readonly happened: (event: Happened) => void;
|
|
22
|
+
private write;
|
|
23
|
+
}
|
package/dist/status.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// .factory/status.json, rewritten at every change, for a status line or toasts to read.
|
|
2
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { spentOn } from './ledger.js';
|
|
6
|
+
export const statusPath = (root) => join(root, '.factory', 'status.json');
|
|
7
|
+
const KEEP = 10;
|
|
8
|
+
const StatusSchema = z.looseObject({
|
|
9
|
+
recent: z.array(z.looseObject({ seq: z.int() }))
|
|
10
|
+
});
|
|
11
|
+
export function readStatus(path) {
|
|
12
|
+
try {
|
|
13
|
+
const value = JSON.parse(readFileSync(path, 'utf8'));
|
|
14
|
+
return StatusSchema.safeParse(value).success ? value : null;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export class StatusFile {
|
|
21
|
+
o;
|
|
22
|
+
now = { kind: 'off' };
|
|
23
|
+
recent;
|
|
24
|
+
constructor(o) {
|
|
25
|
+
this.o = o;
|
|
26
|
+
// the events go on from the last run's, so a reader that saw them does not toast them again
|
|
27
|
+
this.recent = readStatus(o.path)?.recent ?? [];
|
|
28
|
+
}
|
|
29
|
+
doing = (doing) => {
|
|
30
|
+
this.now = doing;
|
|
31
|
+
this.write();
|
|
32
|
+
};
|
|
33
|
+
happened = (event) => {
|
|
34
|
+
const seq = (this.recent.at(-1)?.seq ?? 0) + 1;
|
|
35
|
+
const at = this.o.now().toISOString();
|
|
36
|
+
this.recent = [...this.recent, { ...event, seq, at }].slice(-KEEP);
|
|
37
|
+
this.write();
|
|
38
|
+
};
|
|
39
|
+
write() {
|
|
40
|
+
const now = this.o.now();
|
|
41
|
+
const status = {
|
|
42
|
+
at: now.toISOString(),
|
|
43
|
+
pid: this.o.pid,
|
|
44
|
+
spentTodayUsd: spentOn(this.o.ledger, now),
|
|
45
|
+
dailyUsd: this.o.dailyUsd,
|
|
46
|
+
doing: this.now,
|
|
47
|
+
recent: this.recent,
|
|
48
|
+
codeMoved: null
|
|
49
|
+
};
|
|
50
|
+
mkdirSync(dirname(this.o.path), { recursive: true });
|
|
51
|
+
const temp = `${this.o.path}.${String(this.o.pid)}.tmp`;
|
|
52
|
+
writeFileSync(temp, `${JSON.stringify(status, null, '\t')}\n`);
|
|
53
|
+
renameSync(temp, this.o.path);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Signal } from './signals.js';
|
|
2
|
+
export type ItemId = string;
|
|
3
|
+
export interface Feature {
|
|
4
|
+
readonly id: ItemId;
|
|
5
|
+
readonly title: string;
|
|
6
|
+
readonly body: string;
|
|
7
|
+
}
|
|
8
|
+
export interface Ticket {
|
|
9
|
+
readonly id: ItemId;
|
|
10
|
+
readonly title: string;
|
|
11
|
+
readonly body: string;
|
|
12
|
+
readonly open: boolean;
|
|
13
|
+
readonly signals: readonly Signal[];
|
|
14
|
+
readonly blockedBy: readonly Blocker[];
|
|
15
|
+
}
|
|
16
|
+
export interface Blocker {
|
|
17
|
+
readonly id: ItemId;
|
|
18
|
+
readonly open: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface Comment {
|
|
21
|
+
readonly author: string;
|
|
22
|
+
readonly body: string;
|
|
23
|
+
readonly at: string;
|
|
24
|
+
readonly trusted: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface TicketReference {
|
|
27
|
+
readonly id: ItemId;
|
|
28
|
+
readonly text: string;
|
|
29
|
+
}
|
|
30
|
+
export interface Standing {
|
|
31
|
+
readonly assignees: readonly string[];
|
|
32
|
+
readonly signals: readonly Signal[];
|
|
33
|
+
}
|
|
34
|
+
export interface Tracker {
|
|
35
|
+
me(): Promise<string>;
|
|
36
|
+
assignedFeatures(person: string): Promise<Feature[]>;
|
|
37
|
+
feature(id: ItemId): Promise<Feature>;
|
|
38
|
+
standing(id: ItemId): Promise<Standing>;
|
|
39
|
+
tickets(featureId: ItemId): Promise<Ticket[]>;
|
|
40
|
+
comments(id: ItemId): Promise<Comment[]>;
|
|
41
|
+
addSignal(id: ItemId, signal: Signal): Promise<void>;
|
|
42
|
+
removeSignal(id: ItemId, signal: Signal): Promise<void>;
|
|
43
|
+
comment(id: ItemId, body: string): Promise<void>;
|
|
44
|
+
reference(id: ItemId): TicketReference;
|
|
45
|
+
}
|
|
46
|
+
export interface ItemText {
|
|
47
|
+
readonly title: string;
|
|
48
|
+
readonly body: string;
|
|
49
|
+
}
|
|
50
|
+
export interface FeatureWriter {
|
|
51
|
+
createFeature(feature: ItemText): Promise<ItemId>;
|
|
52
|
+
createTicket(featureId: ItemId, ticket: ItemText): Promise<ItemId>;
|
|
53
|
+
block(id: ItemId, blocker: ItemId): Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
export declare const FEATURE_WRITER_METHODS: readonly (keyof FeatureWriter)[];
|
|
56
|
+
export declare function canCreate(tracker: Tracker): tracker is Tracker & FeatureWriter;
|
package/dist/tracker.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Typed as a record, so a method added to FeatureWriter fails to compile until it is listed here.
|
|
2
|
+
const WRITER_METHODS = {
|
|
3
|
+
createFeature: true,
|
|
4
|
+
createTicket: true,
|
|
5
|
+
block: true
|
|
6
|
+
};
|
|
7
|
+
export const FEATURE_WRITER_METHODS = Object.keys(WRITER_METHODS);
|
|
8
|
+
export function canCreate(tracker) {
|
|
9
|
+
const methods = tracker;
|
|
10
|
+
return FEATURE_WRITER_METHODS.every((name) => typeof methods[name] === 'function');
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=tracker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracker.js","sourceRoot":"","sources":["../src/tracker.ts"],"names":[],"mappings":"AAkFA,kGAAkG;AAClG,MAAM,cAAc,GAAsC;IACzD,aAAa,EAAE,IAAI;IACnB,YAAY,EAAE,IAAI;IAClB,KAAK,EAAE,IAAI;CACX,CAAC;AAEF,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC,IAAI,CAChD,cAAc,CACsB,CAAC;AAEtC,MAAM,UAAU,SAAS,CAAC,OAAgB;IACzC,MAAM,OAAO,GAAG,OAA6C,CAAC;IAC9D,OAAO,sBAAsB,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,OAAO,CAAC,IAAI,CAAC,KAAK,UAAU,CAAC,CAAC;AACpF,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Config } from './config.js';
|
|
2
|
+
import type { ItemId } from './tracker.js';
|
|
3
|
+
export type WorkflowConfig = NonNullable<Config['workflows']>[string];
|
|
4
|
+
export type StepConfig = WorkflowConfig['steps'][number];
|
|
5
|
+
export type StepPoint = StepConfig['point'];
|
|
6
|
+
export declare function workflowLine(body: string): string | undefined;
|
|
7
|
+
export type Chosen = {
|
|
8
|
+
readonly kind: 'none';
|
|
9
|
+
} | {
|
|
10
|
+
readonly kind: 'workflow';
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly workflow: WorkflowConfig;
|
|
13
|
+
} | {
|
|
14
|
+
readonly kind: 'refused';
|
|
15
|
+
readonly why: string;
|
|
16
|
+
};
|
|
17
|
+
export interface WorkflowSettings {
|
|
18
|
+
readonly workflows?: Readonly<Record<string, WorkflowConfig>>;
|
|
19
|
+
readonly defaultWorkflow?: string;
|
|
20
|
+
readonly brokenWorkflows?: Readonly<Record<string, string>>;
|
|
21
|
+
}
|
|
22
|
+
export declare function chooseWorkflow(settings: WorkflowSettings, body: string): Chosen;
|
|
23
|
+
export declare const stepKey: (workflow: string, step: string, ticket: ItemId | null) => string;
|
|
24
|
+
export declare function skillsNamed(prompt: string): string[];
|
|
25
|
+
export interface StepReport {
|
|
26
|
+
readonly step: StepConfig;
|
|
27
|
+
readonly ticket: ItemId | null;
|
|
28
|
+
readonly state: 'done' | 'unmet' | 'undone';
|
|
29
|
+
readonly why?: string;
|
|
30
|
+
readonly rounds?: number;
|
|
31
|
+
readonly left: readonly string[];
|
|
32
|
+
}
|
|
33
|
+
export declare function workflowSection(name: string, reports: readonly StepReport[], ref: (id: ItemId) => string): string | null;
|