atris 3.52.0 → 3.55.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/FOR_AGENTS.md +7 -0
- package/README.md +1 -1
- package/atris/policies/ANTISLOP.md +2 -1
- package/atris/skills/copy-editor/SKILL.md +2 -1
- package/atris/skills/design/SKILL.md +1 -1
- package/atris/skills/engines/SKILL.md +8 -7
- package/atris/skills/x-search/SKILL.md +24 -22
- package/atris/skills/youtube/SKILL.md +89 -40
- package/atris/team/_template/SOUL.md +40 -0
- package/atris/team/validator/MEMBER.md +1 -2
- package/ax +592 -91
- package/bin/atris +21 -0
- package/bin/atris.js +580 -378
- package/commands/api-key.js +170 -0
- package/commands/auth.js +453 -43
- package/commands/autoland.js +3 -1
- package/commands/autopilot-front.js +58 -11
- package/commands/avail.js +5 -4
- package/commands/balance.js +55 -0
- package/commands/bench.js +29 -1
- package/commands/brain.js +63 -12
- package/commands/brainstorm.js +18 -0
- package/commands/business.js +149 -1
- package/commands/chat-scan.js +17 -4
- package/commands/close.js +5 -1
- package/commands/computer.js +2 -2
- package/commands/doctor.js +174 -0
- package/commands/dream.js +7 -0
- package/commands/drill.js +34 -2
- package/commands/engine.js +47 -35
- package/commands/errors.js +58 -8
- package/commands/experiments.js +14 -0
- package/commands/feedback.js +61 -2
- package/commands/fleet-report.js +27 -7
- package/commands/fleet.js +98 -11
- package/commands/founder.js +60 -21
- package/commands/human-missions.js +79 -4
- package/commands/improve.js +54 -4
- package/commands/init.js +68 -92
- package/commands/install.js +40 -0
- package/commands/integrations.js +399 -30
- package/commands/interview.js +8 -0
- package/commands/land.js +48 -17
- package/commands/learn.js +29 -4
- package/commands/log.js +51 -0
- package/commands/member.js +68 -10
- package/commands/mission.js +339 -74
- package/commands/now.js +25 -6
- package/commands/orb.js +97 -2
- package/commands/pack.js +83 -9
- package/commands/playbook.js +381 -0
- package/commands/plugin.js +16 -0
- package/commands/radar.js +96 -40
- package/commands/recap.js +128 -21
- package/commands/release.js +102 -2
- package/commands/review.js +5 -10
- package/commands/revisions.js +55 -0
- package/commands/run-front.js +12 -4
- package/commands/scout.js +12 -1
- package/commands/search.js +123 -28
- package/commands/sign.js +2 -2
- package/commands/signup.js +12 -3
- package/commands/site-deploy.js +17 -4
- package/commands/site.js +2 -2
- package/commands/skill-eval.js +289 -0
- package/commands/skill.js +56 -6
- package/commands/social.js +603 -0
- package/commands/spaceship.js +69 -6
- package/commands/stream.js +8 -1
- package/commands/study.js +32 -19
- package/commands/sync.js +28 -22
- package/commands/task.js +418 -60
- package/commands/teach.js +72 -63
- package/commands/terminal.js +25 -9
- package/commands/topup.js +88 -0
- package/commands/truth.js +52 -6
- package/commands/usage.js +91 -0
- package/commands/verify.js +3 -2
- package/commands/version.js +3 -1
- package/commands/voice.js +5 -1
- package/commands/who.js +69 -11
- package/commands/wiki.js +7 -4
- package/commands/wish.js +76 -5
- package/commands/workflow.js +327 -145
- package/commands/worktree.js +1 -0
- package/commands/write.js +5 -0
- package/commands/x-search.js +340 -0
- package/commands/xp.js +5 -1
- package/commands/youtube.js +551 -33
- package/lib/account-bound.js +47 -0
- package/lib/auto-accept-certified.js +10 -2
- package/lib/auto-lessons.js +186 -0
- package/lib/autoland.js +4 -2
- package/lib/chat-log-scan.js +7 -4
- package/lib/cli-json.js +77 -0
- package/lib/cli-scope.js +26 -0
- package/lib/codex-flight.js +1 -0
- package/lib/conductor-artifacts.js +1 -1
- package/lib/context-gatherer.js +11 -0
- package/lib/developer-api.js +116 -0
- package/lib/engine-ask.js +11 -3
- package/lib/engine-registry.js +32 -7
- package/lib/feedback-triage.js +274 -0
- package/lib/first-minute.js +398 -0
- package/lib/fleet.js +115 -34
- package/lib/functional-owner.js +22 -0
- package/lib/known-commands.js +31 -5
- package/lib/member-scaffold.js +197 -0
- package/lib/mission-ledger-compact.js +127 -0
- package/lib/mission-root.js +4 -2
- package/lib/mission-runtime-loop.js +30 -2
- package/lib/next-moves.js +34 -34
- package/lib/noninteractive.js +88 -0
- package/lib/permission-grants.js +10 -1
- package/lib/policy-lessons.js +4 -1
- package/lib/revision-metric.js +279 -0
- package/lib/runner-command.js +5 -4
- package/lib/scratch-root.js +48 -0
- package/lib/skill-eval-gate.js +249 -0
- package/lib/task-db.js +22 -2
- package/lib/task-proof.js +75 -9
- package/lib/task-receipt.js +5 -1
- package/lib/verifier-quality.js +69 -0
- package/lib/wish-audit.js +2 -2
- package/lib/wish-delegate.js +10 -1
- package/lib/workspace-scaffold.js +270 -0
- package/package.json +4 -2
- package/scripts/det/checklist-score.js +191 -0
- package/scripts/det/ytsearch +31 -0
- package/scripts/outbound-artifact-gate.js +227 -0
- package/utils/update-check.js +15 -0
package/lib/task-proof.js
CHANGED
|
@@ -8,24 +8,69 @@ const PATH_ONLY_PROOF_RE = /(?:^|[\s'"`])(?:\.{0,2}\/|~\/|\/Users\/|\/private\/|
|
|
|
8
8
|
const RECEIPT_OR_ARTIFACT_RE = /\b(?:receipt|artifact|screenshot|log|trace|path=|file=|bytes=|model=|opened=|https?:\/\/)\b/i;
|
|
9
9
|
const RESULT_PAIR_RE = /\b(?:typecheck|build|smoke|test|pytest|verifier|validation|validated|verified|render|diff|sync|lineage|projection)\b.{0,80}\b(?:pass|passed|failed|green|ok|exit\s*0|reviewed)\b|\b(?:pass|passed|failed|green|ok|exit\s*0|reviewed)\b.{0,80}\b(?:typecheck|build|smoke|test|pytest|verifier|validation|validated|verified|render|diff|sync|lineage|projection)\b/i;
|
|
10
10
|
const SUITE_GREEN_CLAIM_RE = /\b(?:tests|test\s+suite|suite)\s+(?:is|are|was|were)?\s*(?:all\s+)?(?:green|pass(?:es|ed)?|ok)\b|\b(?:all|full|whole|entire)\s+(?:tests?|test\s+suite|suite)\s+(?:is|are|was|were)?\s*(?:green|pass(?:es|ed)?|ok)\b|\ball\s+green\b|\b(?:npm|pnpm|yarn)\s+(?:run\s+)?test\b\x60?(?:\s+(?:\d+\s*\/\s*\d+|all|suite))?\s+(?:is\s+)?(?:green|pass(?:es|ed)?|ok)\b/i;
|
|
11
|
-
const
|
|
11
|
+
const CI_RUN_URL_RE = /https?:\/\/github[.]com\/[^/\s]+\/[^/\s]+\/actions\/runs\/\d+\b/i;
|
|
12
|
+
const CI_RUN_ID_RE = /\brun_id\s*=\s*\d+\b|\brun(?:\s+id)?\s*(?:=|:)?\s*\d+\b/i;
|
|
13
|
+
const CI_RUN_CITATION_RE = new RegExp(`${CI_RUN_URL_RE.source}|${CI_RUN_ID_RE.source}`, 'i');
|
|
12
14
|
const COMMIT_PINNED_LOCAL_VERIFY_RE = /\bcommit\s+[0-9a-f]{7,40}\b/i;
|
|
13
15
|
const EXIT_ZERO_RE = /\bexit(?:ed)?(?:\s+code)?\s*0\b/i;
|
|
14
|
-
const SUITE_GREEN_CITATION_REASON = 'cite
|
|
16
|
+
const SUITE_GREEN_CITATION_REASON = 'cite a fetched CI run URL (--proof-url with --i-fetched), a run id, or a commit-pinned verify command with exit 0 before claiming the suite is green';
|
|
17
|
+
const LOCAL_SUCCESS_PROOF_EXAMPLE = 'local success example: atris task ready <id> --verify "<cmd>" --result "<plain sentence>" (exit 0 plus the written atris/runs/ receipt path is enough; do not paste a CI URL unless atris fetched it or you pass --proof-url with --i-fetched)';
|
|
18
|
+
const FETCHED_PROOF_URL_RE = /\[(?:fetched|i-fetched)\]\s*https?:\/\/github[.]com\/[^/\s]+\/[^/\s]+\/actions\/runs\/\d+\b/i;
|
|
15
19
|
const HUMAN_PROOF_RE = /\b(?:team human approved|human approved|human approval|approved by|accepted by|reviewed by|customer replied|customer approved|customer accepted|replied)\b/i;
|
|
16
20
|
const FILE_ACTION_RE = /\b(?:changed|updated|edited|created|deleted|saved|wrote|patched|reviewed|verified|validated|opened|read)\b/i;
|
|
17
|
-
const VERIFIED_PROOF_RE = /^\[verified\]\s+`[^`]+`\s+passed\s+\(exit 0\)(?:\s|$)/
|
|
21
|
+
const VERIFIED_PROOF_RE = /^\[verified\]\s+`[^`]+`\s+passed\s+\(exit 0\)(?:\s|$)/im;
|
|
22
|
+
const LOCAL_RECEIPT_PATH_RE = /(?:^|[\s])(?:Receipt:\s*)?(?:atris\/runs\/|\.atris\/state\/)[^\s'"`,;)]+\.json\b/i;
|
|
18
23
|
const SECOND_ACTOR_EXECUTED_PROOF_RE = /^Second-actor check:\s+`[^`]+`\s+re-run by [^,]+,\s+exited 0(?:[.\s]|$)/i;
|
|
24
|
+
// Ready/receipt prose may name these. If this process did not run the named
|
|
25
|
+
// command, that sentence is a lie. Keep the list tight: these are the claims
|
|
26
|
+
// that already pass the meaningful-proof floor without executing anything.
|
|
27
|
+
const NAMED_READY_COMMAND_RE = /\b(?:npm test|node --test|git diff --check)\b/i;
|
|
28
|
+
const PROOF_COMMAND_NOT_RUN = 'proof_command_not_run';
|
|
19
29
|
|
|
20
30
|
function compactWhitespace(text) {
|
|
21
31
|
return String(text || '').replace(/\s+/g, ' ').trim();
|
|
22
32
|
}
|
|
23
33
|
|
|
34
|
+
function escapeRegExp(text) {
|
|
35
|
+
return String(text || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function namedUnrunProofCommand(proof, ranCommand = '') {
|
|
39
|
+
const text = String(proof || '');
|
|
40
|
+
const match = text.match(NAMED_READY_COMMAND_RE);
|
|
41
|
+
if (!match) return '';
|
|
42
|
+
const named = compactWhitespace(match[0]);
|
|
43
|
+
const ran = compactWhitespace(ranCommand);
|
|
44
|
+
if (ran && new RegExp(`\\b${escapeRegExp(named)}\\b`, 'i').test(ran)) return '';
|
|
45
|
+
return named;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function unrunNamedProofCommandIssue(proof, ranCommand = '') {
|
|
49
|
+
const named = namedUnrunProofCommand(proof, ranCommand);
|
|
50
|
+
if (!named) return null;
|
|
51
|
+
return {
|
|
52
|
+
reason: PROOF_COMMAND_NOT_RUN,
|
|
53
|
+
detail: `proof names \`${named}\`, but this process did not run it\nuse --verify "${named}" so the named command actually executes`,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function hasFetchedCiCitation(text) {
|
|
58
|
+
return FETCHED_PROOF_URL_RE.test(text);
|
|
59
|
+
}
|
|
60
|
+
|
|
24
61
|
function hasSuiteGreenCitation(text) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
62
|
+
// A pasted actions URL is not proof unless atris fetched it (marked
|
|
63
|
+
// [fetched]/[i-fetched]) or the caller attested with --i-fetched.
|
|
64
|
+
if (hasFetchedCiCitation(text)) return true;
|
|
65
|
+
if (CI_RUN_URL_RE.test(text)) return false;
|
|
66
|
+
if (CI_RUN_ID_RE.test(text)) return true;
|
|
67
|
+
return COMMIT_PINNED_LOCAL_VERIFY_RE.test(text)
|
|
68
|
+
&& COMMAND_PROOF_RE.test(text)
|
|
69
|
+
&& EXIT_ZERO_RE.test(text);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function hasLocalVerifiedReceipt(text) {
|
|
73
|
+
return VERIFIED_PROOF_RE.test(text) && LOCAL_RECEIPT_PATH_RE.test(text);
|
|
29
74
|
}
|
|
30
75
|
|
|
31
76
|
function taskProofState(proof) {
|
|
@@ -34,6 +79,18 @@ function taskProofState(proof) {
|
|
|
34
79
|
if (GENERIC_COMPLETION_PROOF_RE.test(text)) {
|
|
35
80
|
return { ok: false, reason: 'proof must name what was verified, changed, approved, or produced' };
|
|
36
81
|
}
|
|
82
|
+
// An executed --verify that exited 0 and wrote an atris/runs receipt is
|
|
83
|
+
// meaningful proof on its own. Suite-green URL rules apply only to claims.
|
|
84
|
+
if (hasLocalVerifiedReceipt(text)) {
|
|
85
|
+
return { ok: true, reason: 'proof is a local verified command with receipt path' };
|
|
86
|
+
}
|
|
87
|
+
if (CI_RUN_URL_RE.test(text) && !hasFetchedCiCitation(text) && !SUITE_GREEN_CLAIM_RE.test(text)) {
|
|
88
|
+
return {
|
|
89
|
+
ok: false,
|
|
90
|
+
code: 'unfetched_proof_url',
|
|
91
|
+
reason: 'CI run URLs only count after atris fetches them, or with --proof-url and --i-fetched',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
37
94
|
if (SUITE_GREEN_CLAIM_RE.test(text)) {
|
|
38
95
|
if (!hasSuiteGreenCitation(text)) {
|
|
39
96
|
return {
|
|
@@ -42,7 +99,7 @@ function taskProofState(proof) {
|
|
|
42
99
|
reason: SUITE_GREEN_CITATION_REASON,
|
|
43
100
|
};
|
|
44
101
|
}
|
|
45
|
-
return { ok: true, reason: 'proof cites a CI run or commit-pinned local verify' };
|
|
102
|
+
return { ok: true, reason: 'proof cites a fetched CI run or commit-pinned local verify' };
|
|
46
103
|
}
|
|
47
104
|
if (COMMAND_PROOF_RE.test(text)) return { ok: true, reason: 'proof names a command' };
|
|
48
105
|
if (HUMAN_PROOF_RE.test(text)) return { ok: true, reason: 'proof names human/customer approval' };
|
|
@@ -80,7 +137,11 @@ function taskProofLooksExecuted(proof) {
|
|
|
80
137
|
function tailText(text, max = 400) {
|
|
81
138
|
const trimmed = String(text || '').trim();
|
|
82
139
|
if (trimmed.length <= max) return trimmed;
|
|
83
|
-
|
|
140
|
+
// Keep the head as well as the tail: failures name their cause in the first
|
|
141
|
+
// lines (missing dependency, bad import), which a tail-only cut always loses.
|
|
142
|
+
const head = Math.floor(max / 2);
|
|
143
|
+
const tail = max - head;
|
|
144
|
+
return `${trimmed.slice(0, head)}\n...\n${trimmed.slice(-tail)}`;
|
|
84
145
|
}
|
|
85
146
|
|
|
86
147
|
// Run a verifier command and turn its real result into proof. This is the
|
|
@@ -112,9 +173,14 @@ function buildVerifiedProof(verifyCmd, baseProof = '', runner, options = {}) {
|
|
|
112
173
|
}
|
|
113
174
|
|
|
114
175
|
module.exports = {
|
|
176
|
+
tailText,
|
|
115
177
|
taskProofLooksMeaningful,
|
|
116
178
|
taskProofLooksExecuted,
|
|
117
179
|
taskProofState,
|
|
118
180
|
taskProofExecutionState,
|
|
181
|
+
namedUnrunProofCommand,
|
|
182
|
+
unrunNamedProofCommandIssue,
|
|
119
183
|
buildVerifiedProof,
|
|
184
|
+
LOCAL_SUCCESS_PROOF_EXAMPLE,
|
|
185
|
+
PROOF_COMMAND_NOT_RUN,
|
|
120
186
|
};
|
package/lib/task-receipt.js
CHANGED
|
@@ -14,7 +14,11 @@ const { spawnSync } = require('child_process');
|
|
|
14
14
|
function tailText(text, max = 400) {
|
|
15
15
|
const trimmed = String(text || '').trim();
|
|
16
16
|
if (trimmed.length <= max) return trimmed;
|
|
17
|
-
|
|
17
|
+
// Keep the head as well as the tail: failures name their cause in the first
|
|
18
|
+
// lines (missing dependency, bad import), which a tail-only cut always loses.
|
|
19
|
+
const head = Math.floor(max / 2);
|
|
20
|
+
const tail = max - head;
|
|
21
|
+
return `${trimmed.slice(0, head)}\n...\n${trimmed.slice(-tail)}`;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
function gitCommitHash(root) {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Ready/recap need a stronger bar than "command can fail in an empty dir".
|
|
4
|
+
// A bare `test -f` clears the empty-dir probe and still never exercises the
|
|
5
|
+
// change. Allowlist checks that actually run tests, read a symbol, or do a
|
|
6
|
+
// real diff/syntax check.
|
|
7
|
+
|
|
8
|
+
function classifyVerifier(command) {
|
|
9
|
+
const cmd = String(command || '').trim();
|
|
10
|
+
if (!cmd) return { kind: 'empty', ok: false, reason: 'no verifier command' };
|
|
11
|
+
|
|
12
|
+
if (/\b(node\s+--test|npm\s+(?:test|run\s+test(?:\S*)?)|pnpm\s+test|yarn\s+test|pytest\b|go\s+test|cargo\s+test|vitest\b|jest\b)\b/i.test(cmd)) {
|
|
13
|
+
return { kind: 'test_runner', ok: true, reason: 'test runner' };
|
|
14
|
+
}
|
|
15
|
+
if (/\b(git\s+diff\s+--(?:check|exit-code|quiet)|node\s+--check)\b/i.test(cmd)) {
|
|
16
|
+
return { kind: 'diff_check', ok: true, reason: 'diff or syntax check' };
|
|
17
|
+
}
|
|
18
|
+
if (/\b(?:rg|grep)\b/i.test(cmd) && /\S{2,}/.test(cmd.replace(/^(?:rg|grep)\b/i, ''))) {
|
|
19
|
+
return { kind: 'symbol_read', ok: true, reason: 'symbol or content read' };
|
|
20
|
+
}
|
|
21
|
+
if (/\batris\s+(?:verify|drill|slop)\b/i.test(cmd)) {
|
|
22
|
+
return { kind: 'atris_check', ok: true, reason: 'atris check command' };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Bare file/directory existence: passes when the file is there, says nothing
|
|
26
|
+
// about whether the change works.
|
|
27
|
+
if (/^(?:test\s+-[efsdL]\s+\S+|\[\s+-[efsdL]\s+\S+\s*\])(?:\s*(?:&&|\|\|)\s*(?:test\s+-[efsdL]\s+\S+|\[\s+-[efsdL]\s+\S+\s*\]))*$/i.test(cmd)
|
|
28
|
+
|| /^test\s+-[efsdL]\s+\S+$/i.test(cmd)
|
|
29
|
+
|| /^\[\s+-[efsdL]\s+\S+\s*\]$/i.test(cmd)) {
|
|
30
|
+
return {
|
|
31
|
+
kind: 'file_exists',
|
|
32
|
+
ok: false,
|
|
33
|
+
reason: 'file-exists check does not exercise the change; use a test runner, git diff --check, node --check, or rg/grep for a new symbol',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Unknown commands still need the empty-dir falsifier; allow them through
|
|
38
|
+
// that probe. Only bare file-exists is rejected here as not exercising work.
|
|
39
|
+
return {
|
|
40
|
+
kind: 'unknown',
|
|
41
|
+
ok: true,
|
|
42
|
+
reason: 'custom verifier; empty-dir falsifier still applies',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isRealTestRunnerProof(proof) {
|
|
47
|
+
const flat = String(proof || '').replace(/\s+/g, ' ').trim();
|
|
48
|
+
if (!flat) return false;
|
|
49
|
+
return /\b(node\s+--test|npm\s+(?:test|run\s+test(?:\S*)?)|pnpm\s+test|yarn\s+test|pytest\b|go\s+test|cargo\s+test|vitest\b|jest\b)\b/i.test(flat)
|
|
50
|
+
&& /\b(pass|passed|green|ok|0 failures?|\d+\/\d+)\b/i.test(flat);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function quoteVerifierCommand(proof) {
|
|
54
|
+
const flat = String(proof || '').replace(/\s+/g, ' ').trim();
|
|
55
|
+
if (!flat) return null;
|
|
56
|
+
const verified = flat.match(/\[verified\]\s*`([^`]+)`\s*passed/i);
|
|
57
|
+
if (verified) return verified[1].trim();
|
|
58
|
+
const backticked = flat.match(/`([^`]+)`/);
|
|
59
|
+
if (backticked && /\b(test|npm|node|git|rg|grep|pytest|vitest|jest)\b/i.test(backticked[1])) {
|
|
60
|
+
return backticked[1].trim();
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = {
|
|
66
|
+
classifyVerifier,
|
|
67
|
+
isRealTestRunnerProof,
|
|
68
|
+
quoteVerifierCommand,
|
|
69
|
+
};
|
package/lib/wish-audit.js
CHANGED
|
@@ -1047,13 +1047,13 @@ function auditWish(text, root = process.cwd(), options = {}) {
|
|
|
1047
1047
|
} catch {
|
|
1048
1048
|
validator = null;
|
|
1049
1049
|
}
|
|
1050
|
-
if (!executor) {
|
|
1050
|
+
if (!executor && !options.cloudRun) {
|
|
1051
1051
|
const question = 'Which working builder should handle this?';
|
|
1052
1052
|
const answered = answeredRepeatedQuestionValue(text, root, question, 'builder');
|
|
1053
1053
|
if (answered) executor = { id: slugify(answered), name: answered };
|
|
1054
1054
|
else questions.push(question);
|
|
1055
1055
|
}
|
|
1056
|
-
if (!validator) {
|
|
1056
|
+
if (!validator && !options.cloudRun) {
|
|
1057
1057
|
const question = 'Which working reviewer should validate it?';
|
|
1058
1058
|
const answered = answeredRepeatedQuestionValue(text, root, question, 'reviewer');
|
|
1059
1059
|
if (answered) validator = { id: slugify(answered), name: answered };
|
package/lib/wish-delegate.js
CHANGED
|
@@ -1639,9 +1639,18 @@ function applyAnswer(wish, answer, root, options = {}) {
|
|
|
1639
1639
|
answers: [...(wish.answers || []), answer],
|
|
1640
1640
|
};
|
|
1641
1641
|
const auditText = [wish.text, ...(answeredWish.answers || [])].join(' ');
|
|
1642
|
-
|
|
1642
|
+
// A cloud grant runs on the backend, so it never needs a local builder or
|
|
1643
|
+
// reviewer; only clarity questions still apply.
|
|
1644
|
+
const cloudRun = typeof options.cloudDispatch === 'function';
|
|
1645
|
+
const audited = auditWish(auditText, root, { engineOverride, originalText: wish.text, cloudRun });
|
|
1643
1646
|
const audit = bestGuessAfterRepeatedQuestion(answeredWish, audited, root);
|
|
1644
1647
|
if (!audit.ok) return askForInput(answeredWish, audit, root, asJson);
|
|
1648
|
+
// --cloud grants hand the answered wish to the backend instead of a local
|
|
1649
|
+
// engine. The caller supplies the dispatcher so this file stays free of the
|
|
1650
|
+
// cloud client.
|
|
1651
|
+
if (typeof options.cloudDispatch === 'function') {
|
|
1652
|
+
return options.cloudDispatch(answeredWish, auditText, root);
|
|
1653
|
+
}
|
|
1645
1654
|
if (noMission) return captureOnlyWish(answeredWish, audit, root, asJson);
|
|
1646
1655
|
return delegateWish(answeredWish, audit, root, asJson, { oneLap: wish.one_lap === true });
|
|
1647
1656
|
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { ensureWikiScaffold } = require('./wiki');
|
|
4
|
+
|
|
5
|
+
const PACKAGE_ATRIS_MD = path.join(__dirname, '..', 'atris.md');
|
|
6
|
+
|
|
7
|
+
function mapPlaceholder() {
|
|
8
|
+
return '# MAP.md\n\n> Generated by your AI agent after reading atris.md\n\nRun your AI agent with atris.md to populate this file.\n';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function todoPlaceholder() {
|
|
12
|
+
return `# TODO.md
|
|
13
|
+
|
|
14
|
+
> Working task queue for this project. Target state = 0.
|
|
15
|
+
> Note: Daily tasks also appear in \`atris/logs/YYYY/YYYY-MM-DD.md\`
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Backlog
|
|
20
|
+
|
|
21
|
+
- **T1:** Generate MAP.md — scan codebase, create navigation index with file:line refs
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## In Progress
|
|
26
|
+
|
|
27
|
+
(empty)
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Completed
|
|
32
|
+
|
|
33
|
+
(Validator deletes after verification)
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function firstJournalMarkdown(today) {
|
|
40
|
+
return `# Log — ${today}
|
|
41
|
+
|
|
42
|
+
## Completed ✅
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## In Progress 🔄
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Backlog
|
|
51
|
+
|
|
52
|
+
- **T1:** Generate MAP.md — scan codebase, create navigation index with file:line refs
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Notes
|
|
57
|
+
|
|
58
|
+
**Bootstrap:** Say "atris next" to start. After MAP.md is generated, system will brainstorm ideas for your first build.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Inbox
|
|
63
|
+
|
|
64
|
+
`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function posixRel(root, filePath) {
|
|
68
|
+
return path.relative(root, filePath).split(path.sep).join('/');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function writeIfMissing(filePath, content) {
|
|
72
|
+
if (fs.existsSync(filePath)) return false;
|
|
73
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
74
|
+
fs.writeFileSync(filePath, content, { encoding: 'utf8', flag: 'wx' });
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function copyIfMissing(sourcePath, destPath) {
|
|
79
|
+
if (fs.existsSync(destPath) || !fs.existsSync(sourcePath)) return false;
|
|
80
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
81
|
+
fs.copyFileSync(sourcePath, destPath);
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function ensureDir(dirPath) {
|
|
86
|
+
if (fs.existsSync(dirPath)) return false;
|
|
87
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function hasDatedLog(logsDir) {
|
|
92
|
+
if (!fs.existsSync(logsDir)) return false;
|
|
93
|
+
const walk = (dir) => {
|
|
94
|
+
let entries;
|
|
95
|
+
try {
|
|
96
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
97
|
+
} catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
for (const entry of entries) {
|
|
101
|
+
const full = path.join(dir, entry.name);
|
|
102
|
+
if (entry.isDirectory()) {
|
|
103
|
+
if (walk(full)) return true;
|
|
104
|
+
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
};
|
|
110
|
+
return walk(logsDir);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function workspaceBrainPresent(projectRoot = process.cwd()) {
|
|
114
|
+
const atrisDir = path.join(projectRoot, 'atris');
|
|
115
|
+
const required = ['MAP.md', 'TODO.md', 'now.md', 'atris.md', path.join('wiki', 'index.md')];
|
|
116
|
+
if (!required.every((rel) => fs.existsSync(path.join(atrisDir, rel)))) return false;
|
|
117
|
+
try {
|
|
118
|
+
if (!fs.statSync(path.join(atrisDir, 'team')).isDirectory()) return false;
|
|
119
|
+
} catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
return hasDatedLog(path.join(atrisDir, 'logs'));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function ensureWorkspaceBrain(projectRoot = process.cwd(), { now = new Date() } = {}) {
|
|
126
|
+
const created = [];
|
|
127
|
+
const atrisDir = path.join(projectRoot, 'atris');
|
|
128
|
+
const note = (filePath) => created.push(posixRel(projectRoot, filePath));
|
|
129
|
+
|
|
130
|
+
ensureDir(atrisDir);
|
|
131
|
+
|
|
132
|
+
const wikiIndex = path.join(atrisDir, 'wiki', 'index.md');
|
|
133
|
+
const wikiMissing = !fs.existsSync(wikiIndex);
|
|
134
|
+
ensureWikiScaffold(projectRoot);
|
|
135
|
+
if (wikiMissing && fs.existsSync(wikiIndex)) note(wikiIndex);
|
|
136
|
+
|
|
137
|
+
const mapFile = path.join(atrisDir, 'MAP.md');
|
|
138
|
+
if (writeIfMissing(mapFile, mapPlaceholder())) note(mapFile);
|
|
139
|
+
|
|
140
|
+
const todoFile = path.join(atrisDir, 'TODO.md');
|
|
141
|
+
if (writeIfMissing(todoFile, todoPlaceholder())) note(todoFile);
|
|
142
|
+
|
|
143
|
+
const nowFile = path.join(atrisDir, 'now.md');
|
|
144
|
+
if (!fs.existsSync(nowFile)) {
|
|
145
|
+
const { renderDefaultNow } = require('../commands/now');
|
|
146
|
+
if (writeIfMissing(nowFile, renderDefaultNow(projectRoot))) note(nowFile);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const year = String(now.getFullYear());
|
|
150
|
+
const today = now.toISOString().split('T')[0];
|
|
151
|
+
const journalFile = path.join(atrisDir, 'logs', year, `${today}.md`);
|
|
152
|
+
if (writeIfMissing(journalFile, firstJournalMarkdown(today))) note(journalFile);
|
|
153
|
+
|
|
154
|
+
const teamDir = path.join(atrisDir, 'team');
|
|
155
|
+
if (ensureDir(teamDir)) created.push(posixRel(projectRoot, teamDir) + '/');
|
|
156
|
+
|
|
157
|
+
const atrisMd = path.join(atrisDir, 'atris.md');
|
|
158
|
+
if (copyIfMissing(PACKAGE_ATRIS_MD, atrisMd)) note(atrisMd);
|
|
159
|
+
|
|
160
|
+
const taskSeed = seedInitTaskPlane(projectRoot, { todoFile, journalFile });
|
|
161
|
+
|
|
162
|
+
return { created, atrisDir, taskSeed };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Seed the SQLite task plane from TODO.md + today's journal so status/task list agree.
|
|
167
|
+
* Idempotent via source_key and normalized-title dedupe.
|
|
168
|
+
*/
|
|
169
|
+
function seedInitTaskPlane(projectRoot, { todoFile, journalFile } = {}) {
|
|
170
|
+
try {
|
|
171
|
+
const taskDb = require('./task-db');
|
|
172
|
+
const { parseTodoFile, parseSection } = require('./todo-fallback');
|
|
173
|
+
const { parseInboxItems } = require('./file-ops');
|
|
174
|
+
const db = taskDb.open();
|
|
175
|
+
const workspaceRoot = taskDb.workspaceRoot(projectRoot);
|
|
176
|
+
const existing = taskDb.listTasks(db, { workspaceRoot, limit: 500 });
|
|
177
|
+
const knownTitles = new Set(existing.map((row) => taskDb.normalizeTitle(row.title)));
|
|
178
|
+
|
|
179
|
+
const candidates = [];
|
|
180
|
+
const todoPath = todoFile || path.join(projectRoot, 'atris', 'TODO.md');
|
|
181
|
+
if (fs.existsSync(todoPath)) {
|
|
182
|
+
const parsed = parseTodoFile(todoPath);
|
|
183
|
+
for (const t of parsed.backlog || []) {
|
|
184
|
+
candidates.push({ ...t, importStatus: 'open', sourceFile: todoPath });
|
|
185
|
+
}
|
|
186
|
+
for (const t of parsed.inProgress || []) {
|
|
187
|
+
candidates.push({ ...t, importStatus: 'claimed', sourceFile: todoPath });
|
|
188
|
+
}
|
|
189
|
+
for (const t of parsed.review || []) {
|
|
190
|
+
candidates.push({ ...t, importStatus: 'review', sourceFile: todoPath });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const journalPath = journalFile || null;
|
|
195
|
+
if (journalPath && fs.existsSync(journalPath)) {
|
|
196
|
+
const content = fs.readFileSync(journalPath, 'utf8');
|
|
197
|
+
for (const t of parseSection(content, 'Backlog')) {
|
|
198
|
+
candidates.push({ ...t, importStatus: 'open', sourceFile: journalPath });
|
|
199
|
+
}
|
|
200
|
+
for (const t of parseSection(content, 'In Progress')) {
|
|
201
|
+
candidates.push({ ...t, importStatus: 'claimed', sourceFile: journalPath });
|
|
202
|
+
}
|
|
203
|
+
for (const item of parseInboxItems(content)) {
|
|
204
|
+
candidates.push({
|
|
205
|
+
id: `I${item.id}`,
|
|
206
|
+
title: item.text,
|
|
207
|
+
tag: 'inbox',
|
|
208
|
+
importStatus: 'open',
|
|
209
|
+
sourceFile: journalPath,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let inserted = 0;
|
|
215
|
+
let skipped = 0;
|
|
216
|
+
for (const t of candidates) {
|
|
217
|
+
if (!t.title) continue;
|
|
218
|
+
const normalized = taskDb.normalizeTitle(t.title);
|
|
219
|
+
if (knownTitles.has(normalized)) {
|
|
220
|
+
skipped += 1;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const sk = taskDb.sourceKey(t.sourceFile, t.title);
|
|
224
|
+
const result = taskDb.addTask(db, {
|
|
225
|
+
title: t.title,
|
|
226
|
+
tag: t.tag || null,
|
|
227
|
+
workspaceRoot,
|
|
228
|
+
sourceKey: sk,
|
|
229
|
+
status: t.importStatus || 'open',
|
|
230
|
+
claimedBy: t.claimed || null,
|
|
231
|
+
metadata: {
|
|
232
|
+
source: 'init_seed',
|
|
233
|
+
todo_id: t.id || null,
|
|
234
|
+
todo_tags: t.tags || [],
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
if (result.inserted) {
|
|
238
|
+
inserted += 1;
|
|
239
|
+
knownTitles.add(normalized);
|
|
240
|
+
} else {
|
|
241
|
+
skipped += 1;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const rows = taskDb.listTasks(db, { workspaceRoot, limit: 500 });
|
|
246
|
+
const projection = taskDb.taskProjection(db, { workspaceRoot, limit: 500 });
|
|
247
|
+
const projectionPath = path.join(projectRoot, '.atris', 'state', 'tasks.projection.json');
|
|
248
|
+
fs.mkdirSync(path.dirname(projectionPath), { recursive: true });
|
|
249
|
+
fs.writeFileSync(projectionPath, `${JSON.stringify(projection, null, 2)}\n`, 'utf8');
|
|
250
|
+
try {
|
|
251
|
+
fs.writeFileSync(
|
|
252
|
+
path.join(projectRoot, 'atris', 'TODO.md'),
|
|
253
|
+
taskDb.renderTodoMarkdown(rows, { title: 'TODO.md' }),
|
|
254
|
+
'utf8'
|
|
255
|
+
);
|
|
256
|
+
} catch {}
|
|
257
|
+
|
|
258
|
+
return { ok: true, inserted, skipped, count: rows.length, projectionPath };
|
|
259
|
+
} catch (error) {
|
|
260
|
+
return {
|
|
261
|
+
ok: false,
|
|
262
|
+
error: error && error.message ? error.message : String(error),
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
module.exports = {
|
|
268
|
+
ensureWorkspaceBrain,
|
|
269
|
+
workspaceBrainPresent,
|
|
270
|
+
};
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "atris",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.55.0",
|
|
4
4
|
"description": "you say what you want in plain words. atris builds it, checks it, and shows you proof.",
|
|
5
5
|
"main": "bin/atris.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"atris": "bin/atris
|
|
7
|
+
"atris": "bin/atris",
|
|
8
8
|
"ax": "ax"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"cli/*.py",
|
|
14
14
|
"commands/",
|
|
15
15
|
"decks/",
|
|
16
|
+
"scripts/outbound-artifact-gate.js",
|
|
16
17
|
"scripts/agent_worktree.py",
|
|
17
18
|
"scripts/det/",
|
|
18
19
|
"utils/",
|
|
@@ -54,6 +55,7 @@
|
|
|
54
55
|
"atris/team/opus-overnight/START_HERE.md",
|
|
55
56
|
"atris/team/opus-overnight/STEERING.md",
|
|
56
57
|
"atris/team/_template/MEMBER.md",
|
|
58
|
+
"atris/team/_template/SOUL.md",
|
|
57
59
|
"atris/features/_templates/",
|
|
58
60
|
"atris/features/company-brain-sync/",
|
|
59
61
|
"atris/policies/",
|