atris 3.13.0 → 3.14.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 +27 -3
- package/bin/atris.js +23 -7
- package/commands/business.js +393 -6
- package/commands/computer.js +181 -3
- package/commands/pull.js +8 -4
- package/commands/push.js +2 -2
- package/commands/task.js +217 -0
- package/lib/task-db.js +288 -0
- package/lib/todo-fallback.js +142 -0
- package/lib/todo.js +99 -184
- package/package.json +2 -2
- package/cli/__pycache__/atris_code.cpython-314.pyc +0 -0
- package/cli/__pycache__/runtime_guard.cpython-312.pyc +0 -0
- package/cli/__pycache__/runtime_guard.cpython-314.pyc +0 -0
package/commands/computer.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* atris computer --cloud — Open CLOUD workspace mode
|
|
6
6
|
* atris computer wake — Start the computer
|
|
7
7
|
* atris computer sleep — Stop (files persist)
|
|
8
|
+
* atris computer card — Show the local computer card
|
|
8
9
|
* atris computer run <command> — Run bash on EC2 (no LLM)
|
|
9
10
|
* atris computer grep <pattern> — Search files on EC2
|
|
10
11
|
* atris computer ls [path] — List files
|
|
@@ -705,8 +706,8 @@ async function runLocalBridgeOp(token, sessionId, op, timeoutSeconds = 30) {
|
|
|
705
706
|
return data;
|
|
706
707
|
}
|
|
707
708
|
|
|
708
|
-
function readBusinessBinding() {
|
|
709
|
-
const bindingPath = path.join(
|
|
709
|
+
function readBusinessBinding(cwd = process.cwd()) {
|
|
710
|
+
const bindingPath = path.join(cwd, '.atris', 'business.json');
|
|
710
711
|
if (!fs.existsSync(bindingPath)) return null;
|
|
711
712
|
try {
|
|
712
713
|
return JSON.parse(fs.readFileSync(bindingPath, 'utf8'));
|
|
@@ -715,6 +716,160 @@ function readBusinessBinding() {
|
|
|
715
716
|
}
|
|
716
717
|
}
|
|
717
718
|
|
|
719
|
+
function readPackageMeta(cwd = process.cwd()) {
|
|
720
|
+
const packagePath = path.join(cwd, 'package.json');
|
|
721
|
+
if (!fs.existsSync(packagePath)) return null;
|
|
722
|
+
try {
|
|
723
|
+
return JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
724
|
+
} catch {
|
|
725
|
+
return null;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function relIfExists(cwd, target) {
|
|
730
|
+
return fs.existsSync(path.join(cwd, target)) ? target : null;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function detectValidationCommand(cwd = process.cwd(), pkg = null) {
|
|
734
|
+
const meta = pkg || readPackageMeta(cwd);
|
|
735
|
+
const testScript = meta?.scripts?.test;
|
|
736
|
+
if (testScript && !/no test specified/i.test(testScript)) return 'npm test';
|
|
737
|
+
if (fs.existsSync(path.join(cwd, 'pytest.ini')) || fs.existsSync(path.join(cwd, 'pyproject.toml'))) return 'pytest';
|
|
738
|
+
if (fs.existsSync(path.join(cwd, 'Cargo.toml'))) return 'cargo test';
|
|
739
|
+
if (fs.existsSync(path.join(cwd, 'go.mod'))) return 'go test ./...';
|
|
740
|
+
return 'none detected';
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function detectComputerType(cwd = process.cwd(), pkg = null, binding = null) {
|
|
744
|
+
if (binding?.computer_type) return binding.computer_type;
|
|
745
|
+
if (binding?.workspace_type) return binding.workspace_type;
|
|
746
|
+
const meta = pkg || readPackageMeta(cwd);
|
|
747
|
+
if (meta?.bin || fs.existsSync(path.join(cwd, 'bin')) || fs.existsSync(path.join(cwd, 'commands'))) return 'codeops';
|
|
748
|
+
if (fs.existsSync(path.join(cwd, 'docs')) || fs.existsSync(path.join(cwd, 'atris', 'wiki'))) return 'research';
|
|
749
|
+
return 'workspace';
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function buildComputerCard(cwd = process.cwd()) {
|
|
753
|
+
const binding = readBusinessBinding(cwd);
|
|
754
|
+
const pkg = readPackageMeta(cwd);
|
|
755
|
+
const folderName = path.basename(cwd);
|
|
756
|
+
const ownerName = binding?.name || pkg?.name || folderName;
|
|
757
|
+
const ownerType = binding ? 'business' : 'project';
|
|
758
|
+
const computerName = binding?.computer_name || binding?.workspace_name || `${ownerName} computer`;
|
|
759
|
+
const computerType = detectComputerType(cwd, pkg, binding);
|
|
760
|
+
const memory = [
|
|
761
|
+
relIfExists(cwd, 'atris/MAP.md'),
|
|
762
|
+
relIfExists(cwd, 'atris/TODO.md'),
|
|
763
|
+
relIfExists(cwd, 'atris/wiki'),
|
|
764
|
+
relIfExists(cwd, 'atris/logs'),
|
|
765
|
+
].filter(Boolean);
|
|
766
|
+
const artifacts = [
|
|
767
|
+
fs.existsSync(path.join(cwd, 'atris')) ? 'atris/reports/' : null,
|
|
768
|
+
relIfExists(cwd, '.atris/receipts'),
|
|
769
|
+
].filter(Boolean);
|
|
770
|
+
|
|
771
|
+
return {
|
|
772
|
+
ownerName,
|
|
773
|
+
ownerType,
|
|
774
|
+
computerName,
|
|
775
|
+
computerType,
|
|
776
|
+
workspace: cwd,
|
|
777
|
+
loop: 'plan -> do -> review',
|
|
778
|
+
memory,
|
|
779
|
+
validation: detectValidationCommand(cwd, pkg),
|
|
780
|
+
proof: binding ? 'atris computer proof' : 'atris proof run',
|
|
781
|
+
visual: 'atris visualize "<prompt>"',
|
|
782
|
+
artifacts,
|
|
783
|
+
generatedAt: new Date().toISOString(),
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function renderList(items) {
|
|
788
|
+
return items.length ? items.join(', ') : 'none detected';
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function renderComputerCard(card) {
|
|
792
|
+
return [
|
|
793
|
+
'Atris Computer Card',
|
|
794
|
+
'',
|
|
795
|
+
` Owner: ${card.ownerName} (${card.ownerType})`,
|
|
796
|
+
` Computer: ${card.computerName}`,
|
|
797
|
+
` Type: ${card.computerType}`,
|
|
798
|
+
` Workspace: ${card.workspace}`,
|
|
799
|
+
` Loop: ${card.loop}`,
|
|
800
|
+
` Memory: ${renderList(card.memory)}`,
|
|
801
|
+
` Validate: ${card.validation}`,
|
|
802
|
+
` Proof: ${card.proof}`,
|
|
803
|
+
` Visual: ${card.visual}`,
|
|
804
|
+
` Artifacts: ${renderList(card.artifacts)}`,
|
|
805
|
+
].join('\n');
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function renderComputerCardMarkdown(card) {
|
|
809
|
+
return [
|
|
810
|
+
'# Atris Computer Card',
|
|
811
|
+
'',
|
|
812
|
+
`Generated: ${card.generatedAt}`,
|
|
813
|
+
'',
|
|
814
|
+
`- Owner: ${card.ownerName} (${card.ownerType})`,
|
|
815
|
+
`- Computer: ${card.computerName}`,
|
|
816
|
+
`- Type: ${card.computerType}`,
|
|
817
|
+
`- Workspace: ${card.workspace}`,
|
|
818
|
+
`- Loop: ${card.loop}`,
|
|
819
|
+
`- Memory: ${renderList(card.memory)}`,
|
|
820
|
+
`- Validate: ${card.validation}`,
|
|
821
|
+
`- Proof: ${card.proof}`,
|
|
822
|
+
`- Visual: ${card.visual}`,
|
|
823
|
+
`- Artifacts: ${renderList(card.artifacts)}`,
|
|
824
|
+
'',
|
|
825
|
+
].join('\n');
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function parseComputerCardArgs(args = []) {
|
|
829
|
+
const options = { write: false, out: null, help: false };
|
|
830
|
+
for (let i = 0; i < args.length; i++) {
|
|
831
|
+
const arg = args[i];
|
|
832
|
+
if (arg === '--help' || arg === '-h') options.help = true;
|
|
833
|
+
else if (arg === '--write') options.write = true;
|
|
834
|
+
else if (arg === '--out' && args[i + 1]) options.out = args[++i];
|
|
835
|
+
else if (arg.startsWith('--out=')) options.out = arg.slice('--out='.length);
|
|
836
|
+
}
|
|
837
|
+
return options;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
function defaultComputerCardPath(cwd = process.cwd()) {
|
|
841
|
+
if (fs.existsSync(path.join(cwd, 'atris'))) {
|
|
842
|
+
return path.join(cwd, 'atris', 'reports', 'computer-card.md');
|
|
843
|
+
}
|
|
844
|
+
return path.join(cwd, 'computer-card.md');
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function computerCard(args = [], cwd = process.cwd()) {
|
|
848
|
+
const options = parseComputerCardArgs(args);
|
|
849
|
+
if (options.help) {
|
|
850
|
+
console.log('Usage: atris computer card [--write] [--out <path>]');
|
|
851
|
+
console.log('');
|
|
852
|
+
console.log('Show the local owner/computer card for this workspace.');
|
|
853
|
+
return null;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
const card = buildComputerCard(cwd);
|
|
857
|
+
console.log(renderComputerCard(card));
|
|
858
|
+
|
|
859
|
+
if (options.write || options.out) {
|
|
860
|
+
const outputPath = options.out
|
|
861
|
+
? (path.isAbsolute(options.out) ? options.out : path.join(cwd, options.out))
|
|
862
|
+
: defaultComputerCardPath(cwd);
|
|
863
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
864
|
+
fs.writeFileSync(outputPath, renderComputerCardMarkdown(card), 'utf8');
|
|
865
|
+
console.log('');
|
|
866
|
+
console.log(`Wrote ${path.relative(cwd, outputPath) || outputPath}`);
|
|
867
|
+
return outputPath;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
return card;
|
|
871
|
+
}
|
|
872
|
+
|
|
718
873
|
async function resolveBusinessContext(token) {
|
|
719
874
|
const binding = readBusinessBinding();
|
|
720
875
|
if (!binding) return null;
|
|
@@ -2397,6 +2552,11 @@ async function runComputer() {
|
|
|
2397
2552
|
return;
|
|
2398
2553
|
}
|
|
2399
2554
|
|
|
2555
|
+
if (sub === 'card') {
|
|
2556
|
+
computerCard(args.slice(1));
|
|
2557
|
+
return;
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2400
2560
|
if (sub === 'claude' || sub === 'codex') {
|
|
2401
2561
|
computerLocalLegacy(args);
|
|
2402
2562
|
return;
|
|
@@ -2405,6 +2565,15 @@ async function runComputer() {
|
|
|
2405
2565
|
if (sub === '--help') {
|
|
2406
2566
|
console.log('Usage: atris computer [mode|command]');
|
|
2407
2567
|
console.log('');
|
|
2568
|
+
console.log('Atris computers are persistent AI workspaces for scoped jobs.');
|
|
2569
|
+
console.log('');
|
|
2570
|
+
console.log(' Owner = User | Business');
|
|
2571
|
+
console.log(' Owner has many Computers');
|
|
2572
|
+
console.log(' Computer = workspace + files + tools + secrets + memory + agents + validation');
|
|
2573
|
+
console.log('');
|
|
2574
|
+
console.log('Common types: codeops, research, CRM, reporting, recruiting, event ops, support, business ops.');
|
|
2575
|
+
console.log('A business can be a company, lab, collective, community, artist, team, or project.');
|
|
2576
|
+
console.log('');
|
|
2408
2577
|
console.log('First use:');
|
|
2409
2578
|
console.log(' cd ~/arena/atris-business/<business>');
|
|
2410
2579
|
console.log(' atris computer');
|
|
@@ -2412,6 +2581,7 @@ async function runComputer() {
|
|
|
2412
2581
|
console.log('');
|
|
2413
2582
|
console.log('Modes:');
|
|
2414
2583
|
console.log(' (default) Choose CLOUD vs LOCAL when both are available');
|
|
2584
|
+
console.log(' card Show the local owner/computer card, no login required');
|
|
2415
2585
|
console.log(' local Open LOCAL Atris mode; cloud brain edits this folder');
|
|
2416
2586
|
console.log(' proof Run the local-edit + cloud-isolation + audit proof');
|
|
2417
2587
|
console.log(' local-byo Open LOCAL BYO Claude mode; Anthropic tokens, no cloud audit');
|
|
@@ -2443,6 +2613,8 @@ async function runComputer() {
|
|
|
2443
2613
|
console.log('');
|
|
2444
2614
|
console.log('Examples:');
|
|
2445
2615
|
console.log(' atris computer');
|
|
2616
|
+
console.log(' atris computer card --write');
|
|
2617
|
+
console.log(' atris business init "My Lab" # shared owner + first/default computer');
|
|
2446
2618
|
console.log(' atris computer proof');
|
|
2447
2619
|
console.log(' atris computer local');
|
|
2448
2620
|
console.log(' atris computer codex');
|
|
@@ -2522,6 +2694,7 @@ async function runComputer() {
|
|
|
2522
2694
|
|
|
2523
2695
|
switch (sub) {
|
|
2524
2696
|
case 'chat': return computerChat(token, ctx, cloudOptions);
|
|
2697
|
+
case 'card': return computerCard(args.slice(1));
|
|
2525
2698
|
case 'proof': return computerProof(token, ctx, cloudOptions);
|
|
2526
2699
|
case 'status': return computerStatus(token, ctx);
|
|
2527
2700
|
case 'wake': return computerWake(token, ctx);
|
|
@@ -2544,4 +2717,9 @@ async function runComputer() {
|
|
|
2544
2717
|
}
|
|
2545
2718
|
}
|
|
2546
2719
|
|
|
2547
|
-
module.exports = {
|
|
2720
|
+
module.exports = {
|
|
2721
|
+
runComputer,
|
|
2722
|
+
buildComputerCard,
|
|
2723
|
+
renderComputerCard,
|
|
2724
|
+
renderComputerCardMarkdown,
|
|
2725
|
+
};
|
package/commands/pull.js
CHANGED
|
@@ -6,7 +6,7 @@ const { findAllMembers } = require('./member');
|
|
|
6
6
|
const { loadConfig } = require('../utils/config');
|
|
7
7
|
const { getLogPath } = require('../lib/file-ops');
|
|
8
8
|
const { parseJournalSections, mergeSections, reconstructJournal } = require('../lib/journal');
|
|
9
|
-
const { loadBusinesses } = require('./business');
|
|
9
|
+
const { loadBusinesses, businessMatchesSlug } = require('./business');
|
|
10
10
|
const { loadManifest, saveManifest, computeFileHash, buildManifest, computeLocalHashes, threeWayCompare } = require('../lib/manifest');
|
|
11
11
|
const { normalizeWikiOnlyPrefix } = require('../lib/wiki');
|
|
12
12
|
const { emitSyncEvent, startTimer } = require('../lib/sync-telemetry');
|
|
@@ -204,6 +204,7 @@ async function pullBusiness(slug) {
|
|
|
204
204
|
|
|
205
205
|
// Resolve business ID — always refresh from API to avoid stale workspace_id
|
|
206
206
|
let businessId, workspaceId, businessName, resolvedSlug;
|
|
207
|
+
let localSlug = slug;
|
|
207
208
|
const businesses = loadBusinesses();
|
|
208
209
|
|
|
209
210
|
const listResult = await apiRequestJson('/business/', { method: 'GET', token: creds.token });
|
|
@@ -220,7 +221,7 @@ async function pullBusiness(slug) {
|
|
|
220
221
|
}
|
|
221
222
|
} else {
|
|
222
223
|
const match = (listResult.data || []).find(
|
|
223
|
-
b => b
|
|
224
|
+
b => businessMatchesSlug(b, slug, { includeName: true })
|
|
224
225
|
);
|
|
225
226
|
if (!match) {
|
|
226
227
|
console.error(`Business "${slug}" not found.`);
|
|
@@ -230,13 +231,15 @@ async function pullBusiness(slug) {
|
|
|
230
231
|
workspaceId = match.workspace_id;
|
|
231
232
|
businessName = match.name;
|
|
232
233
|
resolvedSlug = match.slug;
|
|
234
|
+
localSlug = businessMatchesSlug(match, slug) ? slug : match.slug;
|
|
233
235
|
|
|
234
236
|
// Update local cache
|
|
235
237
|
businesses[slug] = {
|
|
236
238
|
business_id: businessId,
|
|
237
239
|
workspace_id: workspaceId,
|
|
238
240
|
name: businessName,
|
|
239
|
-
slug:
|
|
241
|
+
slug: localSlug,
|
|
242
|
+
canonical_slug: match.slug,
|
|
240
243
|
added_at: new Date().toISOString(),
|
|
241
244
|
};
|
|
242
245
|
const { saveBusinesses } = require('./business');
|
|
@@ -697,7 +700,8 @@ async function pullBusiness(slug) {
|
|
|
697
700
|
const atrisDir = path.join(outputDir, '.atris');
|
|
698
701
|
fs.mkdirSync(atrisDir, { recursive: true });
|
|
699
702
|
fs.writeFileSync(path.join(atrisDir, 'business.json'), JSON.stringify({
|
|
700
|
-
slug:
|
|
703
|
+
slug: localSlug,
|
|
704
|
+
canonical_slug: resolvedSlug || slug,
|
|
701
705
|
business_id: businessId,
|
|
702
706
|
workspace_id: workspaceId,
|
|
703
707
|
name: businessName,
|
package/commands/push.js
CHANGED
|
@@ -3,7 +3,7 @@ const path = require('path');
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
const { loadCredentials } = require('../utils/auth');
|
|
5
5
|
const { apiRequestJson } = require('../utils/api');
|
|
6
|
-
const { loadBusinesses, saveBusinesses } = require('./business');
|
|
6
|
+
const { loadBusinesses, saveBusinesses, businessMatchesSlug } = require('./business');
|
|
7
7
|
const { loadManifest, saveManifest, buildManifest, computeLocalHashes } = require('../lib/manifest');
|
|
8
8
|
const { normalizeWikiOnlyPrefix } = require('../lib/wiki');
|
|
9
9
|
const { emitSyncEvent, startTimer } = require('../lib/sync-telemetry');
|
|
@@ -92,7 +92,7 @@ async function pushAtris() {
|
|
|
92
92
|
const businesses = loadBusinesses();
|
|
93
93
|
const listResult = await apiRequestJson('/business/', { method: 'GET', token: creds.token });
|
|
94
94
|
if (listResult.ok) {
|
|
95
|
-
const match = (listResult.data || []).find(b => b
|
|
95
|
+
const match = (listResult.data || []).find(b => businessMatchesSlug(b, slug, { includeName: true }));
|
|
96
96
|
if (!match) { console.error(`Business "${slug}" not found.`); process.exit(1); }
|
|
97
97
|
businessId = match.id;
|
|
98
98
|
workspaceId = match.workspace_id;
|
package/commands/task.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// `atris task` — SQLite-backed task plane. TODO.md stays the human-readable
|
|
2
|
+
// board; this gives agents atomic claims and a compact sync row.
|
|
3
|
+
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
|
|
10
|
+
const DEFAULT_OWNER = process.env.ATRIS_AGENT_ID
|
|
11
|
+
|| process.env.USER
|
|
12
|
+
|| os.userInfo().username
|
|
13
|
+
|| 'unknown';
|
|
14
|
+
|
|
15
|
+
let taskDbModule = null;
|
|
16
|
+
|
|
17
|
+
function getTaskDb() {
|
|
18
|
+
if (taskDbModule) return taskDbModule;
|
|
19
|
+
try {
|
|
20
|
+
taskDbModule = require('../lib/task-db');
|
|
21
|
+
return taskDbModule;
|
|
22
|
+
} catch (e) {
|
|
23
|
+
const message = String(e && (e.message || e));
|
|
24
|
+
const missingSqlite = e && (
|
|
25
|
+
e.code === 'ERR_UNKNOWN_BUILTIN_MODULE'
|
|
26
|
+
|| /node:sqlite|No such built-in module/i.test(message)
|
|
27
|
+
);
|
|
28
|
+
if (missingSqlite) {
|
|
29
|
+
console.error('atris task requires Node.js 22+ because it uses built-in node:sqlite.');
|
|
30
|
+
console.error('Use the markdown TODO.md flow on older Node versions.');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
throw e;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function help() {
|
|
38
|
+
console.log(`
|
|
39
|
+
atris task — local agent task plane (SQLite, gitignored)
|
|
40
|
+
|
|
41
|
+
atris task add "<title>" [--tag <tag>] Create a task
|
|
42
|
+
atris task list [--all] [--status <s>] List tasks (default: this workspace)
|
|
43
|
+
atris task claim <id> [--as <owner>] Atomic claim
|
|
44
|
+
atris task done <id> [--failed] Mark complete (or failed)
|
|
45
|
+
atris task import <file> One-shot import from TODO.md
|
|
46
|
+
atris task where Print db path + workspace scope
|
|
47
|
+
atris task help This help
|
|
48
|
+
|
|
49
|
+
Env:
|
|
50
|
+
ATRIS_TASKS_DB Override db path (default ~/.atris/tasks.db)
|
|
51
|
+
ATRIS_AGENT_ID Owner id for claim/done (default: $USER)
|
|
52
|
+
`.trim());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function flag(args, name) {
|
|
56
|
+
const i = args.indexOf(name);
|
|
57
|
+
if (i === -1) return null;
|
|
58
|
+
return args[i + 1] || true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function hasFlag(args, name) {
|
|
62
|
+
return args.indexOf(name) !== -1;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function positional(args) {
|
|
66
|
+
return args.filter((a, i) => {
|
|
67
|
+
if (a.startsWith('--')) return false;
|
|
68
|
+
if (i > 0 && args[i - 1].startsWith('--')) return false;
|
|
69
|
+
return true;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function cmdAdd(args) {
|
|
74
|
+
const pos = positional(args);
|
|
75
|
+
const title = pos.join(' ').trim();
|
|
76
|
+
if (!title) {
|
|
77
|
+
console.error('atris task add: title required');
|
|
78
|
+
process.exit(2);
|
|
79
|
+
}
|
|
80
|
+
const tag = flag(args, '--tag');
|
|
81
|
+
const taskDb = getTaskDb();
|
|
82
|
+
const db = taskDb.open();
|
|
83
|
+
const ws = taskDb.workspaceRoot();
|
|
84
|
+
const result = taskDb.addTask(db, {
|
|
85
|
+
title,
|
|
86
|
+
tag: typeof tag === 'string' ? tag : null,
|
|
87
|
+
workspaceRoot: ws,
|
|
88
|
+
});
|
|
89
|
+
console.log(`${result.id}\t${title}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function cmdList(args) {
|
|
93
|
+
const all = hasFlag(args, '--all');
|
|
94
|
+
const status = flag(args, '--status');
|
|
95
|
+
const taskDb = getTaskDb();
|
|
96
|
+
const db = taskDb.open();
|
|
97
|
+
const rows = taskDb.listTasks(db, {
|
|
98
|
+
workspaceRoot: all ? null : taskDb.workspaceRoot(),
|
|
99
|
+
status: typeof status === 'string' ? status : null,
|
|
100
|
+
limit: 200,
|
|
101
|
+
});
|
|
102
|
+
if (rows.length === 0) {
|
|
103
|
+
console.log('(no tasks)');
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
for (const r of rows) {
|
|
107
|
+
const claim = r.claimed_by ? ` [${r.claimed_by}]` : '';
|
|
108
|
+
const tag = r.tag ? ` #${r.tag}` : '';
|
|
109
|
+
console.log(`${r.status.padEnd(8)} ${r.id}${claim}${tag}\t${r.title}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function cmdClaim(args) {
|
|
114
|
+
const pos = positional(args);
|
|
115
|
+
const id = pos[0];
|
|
116
|
+
if (!id) {
|
|
117
|
+
console.error('atris task claim: id required');
|
|
118
|
+
process.exit(2);
|
|
119
|
+
}
|
|
120
|
+
const owner = flag(args, '--as') || DEFAULT_OWNER;
|
|
121
|
+
const taskDb = getTaskDb();
|
|
122
|
+
const db = taskDb.open();
|
|
123
|
+
const result = taskDb.claimTask(db, { id, claimedBy: String(owner) });
|
|
124
|
+
if (result.claimed) {
|
|
125
|
+
console.log(`claimed ${id} as ${owner}`);
|
|
126
|
+
} else {
|
|
127
|
+
console.error(`claim failed: ${result.reason}${result.claimed_by ? ` (held by ${result.claimed_by})` : ''}`);
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function cmdDone(args) {
|
|
133
|
+
const pos = positional(args);
|
|
134
|
+
const id = pos[0];
|
|
135
|
+
if (!id) {
|
|
136
|
+
console.error('atris task done: id required');
|
|
137
|
+
process.exit(2);
|
|
138
|
+
}
|
|
139
|
+
const failed = hasFlag(args, '--failed');
|
|
140
|
+
const taskDb = getTaskDb();
|
|
141
|
+
const db = taskDb.open();
|
|
142
|
+
const result = taskDb.doneTask(db, { id, status: failed ? 'failed' : 'done' });
|
|
143
|
+
if (result.updated) {
|
|
144
|
+
console.log(`${failed ? 'failed' : 'done'} ${id}`);
|
|
145
|
+
} else {
|
|
146
|
+
console.error(`done failed: ${id} not in open|claimed`);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function cmdImport(args) {
|
|
152
|
+
const pos = positional(args);
|
|
153
|
+
const target = pos[0] || 'atris/TODO.md';
|
|
154
|
+
const filePath = path.resolve(target);
|
|
155
|
+
if (!fs.existsSync(filePath)) {
|
|
156
|
+
console.error(`atris task import: file not found: ${filePath}`);
|
|
157
|
+
process.exit(2);
|
|
158
|
+
}
|
|
159
|
+
const { parseTodoFile } = require('../lib/todo-fallback');
|
|
160
|
+
const parsed = parseTodoFile(filePath);
|
|
161
|
+
const taskDb = getTaskDb();
|
|
162
|
+
const db = taskDb.open();
|
|
163
|
+
const ws = taskDb.workspaceRoot();
|
|
164
|
+
const all = [
|
|
165
|
+
...parsed.backlog.map(t => ({ ...t, importStatus: 'open' })),
|
|
166
|
+
...parsed.inProgress.map(t => ({ ...t, importStatus: 'claimed' })),
|
|
167
|
+
];
|
|
168
|
+
let inserted = 0;
|
|
169
|
+
let skipped = 0;
|
|
170
|
+
for (const t of all) {
|
|
171
|
+
if (!t.title) continue;
|
|
172
|
+
const sk = taskDb.sourceKey(filePath, t.title);
|
|
173
|
+
const result = taskDb.addTask(db, {
|
|
174
|
+
title: t.title,
|
|
175
|
+
tag: t.tag || null,
|
|
176
|
+
workspaceRoot: ws,
|
|
177
|
+
sourceKey: sk,
|
|
178
|
+
status: t.importStatus,
|
|
179
|
+
claimedBy: t.claimed || null,
|
|
180
|
+
metadata: { todo_id: t.id, claimed: t.claimed, stage: t.stage, verify: t.verify },
|
|
181
|
+
});
|
|
182
|
+
if (result.inserted) inserted++; else skipped++;
|
|
183
|
+
}
|
|
184
|
+
console.log(`imported ${inserted} new, skipped ${skipped} (already imported), source=${filePath}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function cmdWhere() {
|
|
188
|
+
const taskDb = getTaskDb();
|
|
189
|
+
console.log(`db: ${taskDb.getDbPath()}`);
|
|
190
|
+
console.log(`workspace: ${taskDb.workspaceRoot()}`);
|
|
191
|
+
console.log(`owner: ${DEFAULT_OWNER}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function run(args) {
|
|
195
|
+
const sub = (args && args[0]) || 'help';
|
|
196
|
+
const rest = (args || []).slice(1);
|
|
197
|
+
switch (sub) {
|
|
198
|
+
case 'add': return cmdAdd(rest);
|
|
199
|
+
case 'list': return cmdList(rest);
|
|
200
|
+
case 'ls': return cmdList(rest);
|
|
201
|
+
case 'claim': return cmdClaim(rest);
|
|
202
|
+
case 'done': return cmdDone(rest);
|
|
203
|
+
case 'fail': return cmdDone([...rest, '--failed']);
|
|
204
|
+
case 'import': return cmdImport(rest);
|
|
205
|
+
case 'where': return cmdWhere();
|
|
206
|
+
case 'help':
|
|
207
|
+
case '--help':
|
|
208
|
+
case '-h':
|
|
209
|
+
return help();
|
|
210
|
+
default:
|
|
211
|
+
console.error(`atris task: unknown subcommand "${sub}"`);
|
|
212
|
+
help();
|
|
213
|
+
process.exit(2);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
module.exports = { run };
|