@lamentis/naome 1.3.17 → 1.4.1
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/Cargo.lock +2 -2
- package/crates/naome-cli/Cargo.toml +1 -1
- package/crates/naome-cli/src/architecture_commands.rs +16 -3
- package/crates/naome-cli/src/main.rs +10 -1
- package/crates/naome-cli/src/task_commands/common.rs +32 -0
- package/crates/naome-cli/src/task_commands/readiness.rs +40 -0
- package/crates/naome-cli/src/task_commands/record.rs +134 -0
- package/crates/naome-cli/src/task_commands/repair.rs +30 -0
- package/crates/naome-cli/src/task_commands/scope_request.rs +24 -0
- package/crates/naome-cli/src/task_commands/timeline.rs +71 -0
- package/crates/naome-cli/src/task_commands.rs +69 -1
- package/crates/naome-cli/tests/task_cli.rs +58 -0
- package/crates/naome-cli/tests/task_cli_agent_controls.rs +217 -0
- package/crates/naome-cli/tests/task_cli_control.rs +126 -0
- package/crates/naome-cli/tests/task_cli_support/mod.rs +150 -0
- package/crates/naome-core/Cargo.toml +1 -1
- package/crates/naome-core/src/architecture/output.rs +196 -0
- package/crates/naome-core/src/architecture/scan/cache.rs +1 -1
- package/crates/naome-core/src/architecture.rs +1 -0
- package/crates/naome-core/src/lib.rs +15 -9
- package/crates/naome-core/src/task_state/mod.rs +10 -0
- package/crates/naome-core/src/task_state/status/agent_model.rs +76 -0
- package/crates/naome-core/src/task_state/status/control/action.rs +87 -0
- package/crates/naome-core/src/task_state/status/control/exit_code.rs +32 -0
- package/crates/naome-core/src/task_state/status/control/loop_state.rs +70 -0
- package/crates/naome-core/src/task_state/status/control/policy.rs +31 -0
- package/crates/naome-core/src/task_state/status/control/proof_recording.rs +25 -0
- package/crates/naome-core/src/task_state/status/control/recovery.rs +19 -0
- package/crates/naome-core/src/task_state/status/control/repair.rs +125 -0
- package/crates/naome-core/src/task_state/status/control/shared.rs +25 -0
- package/crates/naome-core/src/task_state/status/control.rs +16 -0
- package/crates/naome-core/src/task_state/status/git.rs +133 -0
- package/crates/naome-core/src/task_state/status/model.rs +150 -0
- package/crates/naome-core/src/task_state/status/proof.rs +167 -0
- package/crates/naome-core/src/task_state/status/proof_read.rs +150 -0
- package/crates/naome-core/src/task_state/status/report.rs +148 -0
- package/crates/naome-core/src/task_state/status/report_context.rs +126 -0
- package/crates/naome-core/src/task_state/status/report_support.rs +117 -0
- package/crates/naome-core/src/task_state/status/scope.rs +111 -0
- package/crates/naome-core/src/task_state/status/transition.rs +73 -0
- package/crates/naome-core/src/task_state/status.rs +23 -0
- package/crates/naome-core/src/task_state/status_output.rs +103 -0
- package/crates/naome-core/tests/architecture_cache.rs +1 -1
- package/crates/naome-core/tests/architecture_config.rs +68 -1
- package/crates/naome-core/tests/task_state_support/mod.rs +15 -1
- package/crates/naome-core/tests/task_state_support/states.rs +4 -0
- package/crates/naome-core/tests/task_status.rs +301 -0
- package/crates/naome-core/tests/task_status_git.rs +141 -0
- package/native/darwin-arm64/naome +0 -0
- package/native/linux-x64/naome +0 -0
- package/package.json +1 -1
- package/templates/naome-root/.naome/bin/check-harness-health.js +1 -1
- package/templates/naome-root/.naome/bin/check-task-state.js +1 -1
- package/templates/naome-root/.naome/manifest.json +2 -2
- package/templates/naome-root/docs/naome/architecture-fitness.md +23 -23
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
use serde::{Deserialize, Serialize};
|
|
2
|
+
use serde_json::{json, Value};
|
|
3
|
+
use std::collections::BTreeSet;
|
|
4
|
+
use std::path::Path;
|
|
2
5
|
|
|
3
6
|
use super::model::{Severity, SourceRange};
|
|
4
7
|
use super::scan::ArchitectureScanReport;
|
|
5
8
|
|
|
9
|
+
const SARIF_REPO_ROOT_BASE_ID: &str = "REPO_ROOT";
|
|
10
|
+
const ARCHITECTURE_CONFIG_PATH: &str = "naome.arch.yaml";
|
|
11
|
+
|
|
6
12
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
7
13
|
#[serde(rename_all = "camelCase")]
|
|
8
14
|
pub struct ViolationSummary {
|
|
@@ -216,3 +222,193 @@ pub fn format_architecture_scan(report: &ArchitectureScanReport) -> String {
|
|
|
216
222
|
}
|
|
217
223
|
output
|
|
218
224
|
}
|
|
225
|
+
|
|
226
|
+
pub fn architecture_validation_sarif(report: &ArchitectureValidation) -> Value {
|
|
227
|
+
architecture_validation_sarif_with_root(report, Path::new("/"))
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
pub fn architecture_validation_sarif_with_root(
|
|
231
|
+
report: &ArchitectureValidation,
|
|
232
|
+
repo_root: &Path,
|
|
233
|
+
) -> Value {
|
|
234
|
+
let mut rule_ids = ARCHITECTURE_RULE_IDS
|
|
235
|
+
.iter()
|
|
236
|
+
.map(|rule| rule.to_string())
|
|
237
|
+
.collect::<BTreeSet<_>>();
|
|
238
|
+
for finding in &report.config_findings {
|
|
239
|
+
rule_ids.insert(finding.id.clone());
|
|
240
|
+
}
|
|
241
|
+
for violation in &report.violations {
|
|
242
|
+
rule_ids.insert(violation.id.clone());
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
let rules = rule_ids
|
|
246
|
+
.iter()
|
|
247
|
+
.map(|id| {
|
|
248
|
+
json!({
|
|
249
|
+
"id": id,
|
|
250
|
+
"name": id,
|
|
251
|
+
"shortDescription": {
|
|
252
|
+
"text": id
|
|
253
|
+
},
|
|
254
|
+
"helpUri": "https://github.com/Lamentis-O/naome/blob/main/docs/naome/architecture-fitness.md"
|
|
255
|
+
})
|
|
256
|
+
})
|
|
257
|
+
.collect::<Vec<_>>();
|
|
258
|
+
|
|
259
|
+
let mut results = Vec::new();
|
|
260
|
+
for violation in &report.violations {
|
|
261
|
+
results.push(sarif_result_for_violation(violation));
|
|
262
|
+
}
|
|
263
|
+
for finding in &report.config_findings {
|
|
264
|
+
results.push(sarif_result_for_config_finding(finding));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
json!({
|
|
268
|
+
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
|
|
269
|
+
"version": "2.1.0",
|
|
270
|
+
"runs": [
|
|
271
|
+
{
|
|
272
|
+
"tool": {
|
|
273
|
+
"driver": {
|
|
274
|
+
"name": "NAOME Architecture Fitness",
|
|
275
|
+
"informationUri": "https://github.com/Lamentis-O/naome",
|
|
276
|
+
"rules": rules
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
"originalUriBaseIds": {
|
|
280
|
+
"REPO_ROOT": {
|
|
281
|
+
"uri": file_uri_for_directory(repo_root)
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
"results": results,
|
|
285
|
+
"properties": {
|
|
286
|
+
"status": report.status,
|
|
287
|
+
"filesScanned": report.files_scanned,
|
|
288
|
+
"graphNodes": report.graph_nodes,
|
|
289
|
+
"graphEdges": report.graph_edges,
|
|
290
|
+
"changedOnlyRequested": report.changed_only_requested,
|
|
291
|
+
"changedOnlyMode": report.changed_only_mode,
|
|
292
|
+
"changedOnlyDegradedToFullScan": report.changed_only_degraded_to_full_scan,
|
|
293
|
+
"changedOnlyDegradationReason": report.changed_only_degradation_reason
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
]
|
|
297
|
+
})
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
fn sarif_result_for_violation(violation: &ArchitectureViolation) -> Value {
|
|
301
|
+
json!({
|
|
302
|
+
"ruleId": violation.id,
|
|
303
|
+
"level": sarif_level_for_severity(violation.severity),
|
|
304
|
+
"message": {
|
|
305
|
+
"text": violation.message
|
|
306
|
+
},
|
|
307
|
+
"locations": sarif_locations(
|
|
308
|
+
violation.path.as_deref().or_else(|| violation_file_endpoint(violation)),
|
|
309
|
+
violation.source_range.as_ref()
|
|
310
|
+
),
|
|
311
|
+
"properties": {
|
|
312
|
+
"type": violation.violation_type,
|
|
313
|
+
"from": violation.from,
|
|
314
|
+
"to": violation.to,
|
|
315
|
+
"suggestion": violation.suggestion,
|
|
316
|
+
"agentInstruction": violation.agent_instruction
|
|
317
|
+
}
|
|
318
|
+
})
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
fn sarif_result_for_config_finding(finding: &ArchitectureConfigFinding) -> Value {
|
|
322
|
+
let path = config_finding_path(finding);
|
|
323
|
+
json!({
|
|
324
|
+
"ruleId": finding.id,
|
|
325
|
+
"level": sarif_level_for_config_finding(&finding.severity),
|
|
326
|
+
"message": {
|
|
327
|
+
"text": finding.message
|
|
328
|
+
},
|
|
329
|
+
"locations": sarif_locations(Some(path.as_str()), None),
|
|
330
|
+
"properties": {
|
|
331
|
+
"subject": finding.subject,
|
|
332
|
+
"suggestion": finding.suggestion,
|
|
333
|
+
"agentInstruction": finding.agent_instruction
|
|
334
|
+
}
|
|
335
|
+
})
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
fn sarif_locations(path: Option<&str>, source_range: Option<&SourceRange>) -> Vec<Value> {
|
|
339
|
+
let Some(path) = path else {
|
|
340
|
+
return Vec::new();
|
|
341
|
+
};
|
|
342
|
+
let mut physical_location = json!({
|
|
343
|
+
"artifactLocation": {
|
|
344
|
+
"uri": path,
|
|
345
|
+
"uriBaseId": SARIF_REPO_ROOT_BASE_ID
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
if let Some(range) = source_range {
|
|
349
|
+
physical_location["region"] = json!({
|
|
350
|
+
"startLine": range.start_line,
|
|
351
|
+
"startColumn": range.start_column,
|
|
352
|
+
"endLine": range.end_line,
|
|
353
|
+
"endColumn": range.end_column
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
vec![json!({
|
|
357
|
+
"physicalLocation": physical_location
|
|
358
|
+
})]
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
fn violation_file_endpoint(violation: &ArchitectureViolation) -> Option<&str> {
|
|
362
|
+
[&violation.from, &violation.to]
|
|
363
|
+
.into_iter()
|
|
364
|
+
.flatten()
|
|
365
|
+
.find_map(|endpoint| endpoint.strip_prefix("file:"))
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
fn config_finding_path(finding: &ArchitectureConfigFinding) -> String {
|
|
369
|
+
finding
|
|
370
|
+
.subject
|
|
371
|
+
.strip_prefix("file:")
|
|
372
|
+
.unwrap_or(ARCHITECTURE_CONFIG_PATH)
|
|
373
|
+
.to_string()
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
fn sarif_level_for_severity(severity: Severity) -> &'static str {
|
|
377
|
+
match severity {
|
|
378
|
+
Severity::Error => "error",
|
|
379
|
+
Severity::Warning => "warning",
|
|
380
|
+
Severity::Info => "note",
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
fn sarif_level_for_config_finding(severity: &str) -> &'static str {
|
|
385
|
+
match severity {
|
|
386
|
+
"error" => "error",
|
|
387
|
+
"warning" => "warning",
|
|
388
|
+
_ => "note",
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
fn file_uri_for_directory(path: &Path) -> String {
|
|
393
|
+
let mut normalized = path.to_string_lossy().replace('\\', "/");
|
|
394
|
+
if !normalized.starts_with('/') {
|
|
395
|
+
normalized.insert(0, '/');
|
|
396
|
+
}
|
|
397
|
+
while normalized.ends_with('/') && normalized.len() > 1 {
|
|
398
|
+
normalized.pop();
|
|
399
|
+
}
|
|
400
|
+
format!("file://{}/", encode_uri_path(&normalized))
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
fn encode_uri_path(path: &str) -> String {
|
|
404
|
+
let mut encoded = String::new();
|
|
405
|
+
for byte in path.as_bytes() {
|
|
406
|
+
match *byte {
|
|
407
|
+
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'/' | b'-' | b'.' | b'_' | b'~' => {
|
|
408
|
+
encoded.push(*byte as char)
|
|
409
|
+
}
|
|
410
|
+
value => encoded.push_str(&format!("%{value:02X}")),
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
encoded
|
|
414
|
+
}
|
|
@@ -9,7 +9,7 @@ use super::FileFact;
|
|
|
9
9
|
use crate::models::NaomeError;
|
|
10
10
|
|
|
11
11
|
const CACHE_SCHEMA: &str = "naome.architecture-cache.v1";
|
|
12
|
-
const EXTRACTOR_VERSION: &str = "architecture-cache-v1.
|
|
12
|
+
const EXTRACTOR_VERSION: &str = "architecture-cache-v1.4.0";
|
|
13
13
|
const CACHE_PATH: &str = ".naome/cache/architecture/cache.json";
|
|
14
14
|
|
|
15
15
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
@@ -20,6 +20,7 @@ pub use model::{
|
|
|
20
20
|
ArchitectureNode, ArchitectureNodeKind, Severity, SourceRange,
|
|
21
21
|
};
|
|
22
22
|
pub use output::{
|
|
23
|
+
architecture_validation_sarif, architecture_validation_sarif_with_root,
|
|
23
24
|
format_architecture_scan, format_architecture_validation, ArchitectureAgentFeedback,
|
|
24
25
|
ArchitectureConfigFinding, ArchitectureValidation, ArchitectureViolation, ViolationSummary,
|
|
25
26
|
ARCHITECTURE_RULE_IDS,
|
|
@@ -21,13 +21,14 @@ mod verification_contract_policy;
|
|
|
21
21
|
mod workflow;
|
|
22
22
|
|
|
23
23
|
pub use architecture::{
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
LayerConfig, RuleConfig, Severity, SourceRange,
|
|
24
|
+
architecture_validation_sarif, architecture_validation_sarif_with_root, config_findings_for,
|
|
25
|
+
default_architecture_config_text, format_architecture_explain, format_architecture_scan,
|
|
26
|
+
format_architecture_validation, scan_architecture, validate_architecture,
|
|
27
|
+
ArchitectureAgentFeedback, ArchitectureConfig, ArchitectureConfigFinding, ArchitectureEdge,
|
|
28
|
+
ArchitectureEdgeKind, ArchitectureGraph, ArchitectureMetadata, ArchitectureNode,
|
|
29
|
+
ArchitectureNodeKind, ArchitectureScanOptions, ArchitectureScanReport, ArchitectureValidation,
|
|
30
|
+
ArchitectureViolation, ContextConfig, LayerConfig, RuleConfig, Severity, SourceRange,
|
|
31
|
+
ViolationSummary, ARCHITECTURE_RULE_IDS,
|
|
31
32
|
};
|
|
32
33
|
pub use context::{
|
|
33
34
|
select_context_for_changed_paths, select_context_for_prompt, ContextBudgetLedger,
|
|
@@ -67,8 +68,13 @@ pub use task_ledger::{
|
|
|
67
68
|
TaskLedgerProjection, TaskLedgerStatus,
|
|
68
69
|
};
|
|
69
70
|
pub use task_state::{
|
|
70
|
-
completed_task_commit_paths,
|
|
71
|
-
|
|
71
|
+
completed_task_commit_paths, format_task_proof_plan, format_task_status, task_proof_plan,
|
|
72
|
+
task_status_exit_code, task_status_report, task_transition_readiness, validate_task_state,
|
|
73
|
+
AgentLoop, NextActionV2, PolicyHints, ProofRecording, ProofRecordingAfterSuccess,
|
|
74
|
+
RecoveryGuidance, RepairPlanItem, TaskFeedback, TaskGitStatus, TaskModeStatus,
|
|
75
|
+
TaskProofPlanReport, TaskProofStatus, TaskRecommendedCommand, TaskScopeStatus, TaskStateMode,
|
|
76
|
+
TaskStateOptions, TaskStateReport, TaskStatusFinding, TaskStatusReportV1,
|
|
77
|
+
TransitionReadinessReport,
|
|
72
78
|
};
|
|
73
79
|
pub use verification::seed_builtin_verification_checks;
|
|
74
80
|
pub use verification_contract::validate_verification_contract;
|
|
@@ -22,6 +22,8 @@ mod proof_types;
|
|
|
22
22
|
mod push_gate;
|
|
23
23
|
mod repair;
|
|
24
24
|
mod shape;
|
|
25
|
+
mod status;
|
|
26
|
+
mod status_output;
|
|
25
27
|
mod task_diff_api;
|
|
26
28
|
mod task_records;
|
|
27
29
|
mod task_references;
|
|
@@ -31,6 +33,14 @@ mod util;
|
|
|
31
33
|
pub use api::validate_task_state;
|
|
32
34
|
pub use completed_refresh::completed_task_harness_refresh_diff;
|
|
33
35
|
pub(crate) use proof_model::{canonical_proof_check_ids, canonical_proofs};
|
|
36
|
+
pub use status::{
|
|
37
|
+
task_proof_plan, task_status_exit_code, task_status_report, task_transition_readiness,
|
|
38
|
+
AgentLoop, NextActionV2, PolicyHints, ProofRecording, ProofRecordingAfterSuccess,
|
|
39
|
+
RecoveryGuidance, RepairPlanItem, TaskFeedback, TaskGitStatus, TaskModeStatus,
|
|
40
|
+
TaskProofPlanReport, TaskProofStatus, TaskRecommendedCommand, TaskScopeStatus,
|
|
41
|
+
TaskStatusFinding, TaskStatusReportV1, TransitionReadinessReport,
|
|
42
|
+
};
|
|
43
|
+
pub use status_output::{format_task_proof_plan, format_task_status};
|
|
34
44
|
pub use task_diff_api::{
|
|
35
45
|
completed_task_commit_diff, completed_task_commit_paths, harness_refresh_diff,
|
|
36
46
|
harness_refresh_with_unrelated_diff,
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
use serde::{Deserialize, Serialize};
|
|
2
|
+
|
|
3
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
4
|
+
#[serde(rename_all = "camelCase")]
|
|
5
|
+
pub struct NextActionV2 {
|
|
6
|
+
#[serde(rename = "type")]
|
|
7
|
+
pub action_type: String,
|
|
8
|
+
pub reason: String,
|
|
9
|
+
pub check_ids: Vec<String>,
|
|
10
|
+
pub commands: Vec<String>,
|
|
11
|
+
pub paths: Vec<String>,
|
|
12
|
+
pub safe_to_execute: bool,
|
|
13
|
+
pub requires_user_approval: bool,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
17
|
+
#[serde(rename_all = "camelCase")]
|
|
18
|
+
pub struct AgentLoop {
|
|
19
|
+
pub state: String,
|
|
20
|
+
pub can_continue_editing: bool,
|
|
21
|
+
pub can_run_checks: bool,
|
|
22
|
+
pub can_record_proof: bool,
|
|
23
|
+
pub can_commit: bool,
|
|
24
|
+
pub must_do_next: Vec<String>,
|
|
25
|
+
pub must_not_do: Vec<String>,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
29
|
+
#[serde(rename_all = "camelCase")]
|
|
30
|
+
pub struct RepairPlanItem {
|
|
31
|
+
pub id: String,
|
|
32
|
+
pub kind: String,
|
|
33
|
+
pub reason: String,
|
|
34
|
+
pub paths: Vec<String>,
|
|
35
|
+
pub check_ids: Vec<String>,
|
|
36
|
+
pub commands: Vec<String>,
|
|
37
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
38
|
+
pub cwd: Option<String>,
|
|
39
|
+
pub safe_to_execute: bool,
|
|
40
|
+
pub requires_user_approval: bool,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
44
|
+
#[serde(rename_all = "camelCase")]
|
|
45
|
+
pub struct ProofRecording {
|
|
46
|
+
pub path_set_id: String,
|
|
47
|
+
pub paths: Vec<String>,
|
|
48
|
+
pub proof_batch_id: String,
|
|
49
|
+
pub checks_to_record: Vec<String>,
|
|
50
|
+
pub after_success: ProofRecordingAfterSuccess,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
54
|
+
#[serde(rename_all = "camelCase")]
|
|
55
|
+
pub struct ProofRecordingAfterSuccess {
|
|
56
|
+
pub record_proof: bool,
|
|
57
|
+
pub instructions: String,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
61
|
+
#[serde(rename_all = "camelCase")]
|
|
62
|
+
pub struct PolicyHints {
|
|
63
|
+
pub may_edit: Vec<String>,
|
|
64
|
+
pub must_inspect_before_edit: Vec<String>,
|
|
65
|
+
pub must_run_after_edit: Vec<String>,
|
|
66
|
+
pub forbidden_actions: Vec<String>,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
70
|
+
#[serde(rename_all = "camelCase")]
|
|
71
|
+
pub struct RecoveryGuidance {
|
|
72
|
+
pub id: String,
|
|
73
|
+
pub reason: String,
|
|
74
|
+
pub safe_next_action: String,
|
|
75
|
+
pub requires_user_approval: bool,
|
|
76
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
use super::super::agent_model::NextActionV2;
|
|
2
|
+
use super::super::model::{
|
|
3
|
+
TaskProofStatus, TaskRecommendedCommand, TaskScopeStatus, TaskStatusFinding,
|
|
4
|
+
};
|
|
5
|
+
use super::shared::{action_check_ids, has_finding, has_git_recovery};
|
|
6
|
+
|
|
7
|
+
pub(in crate::task_state::status) fn next_action_v2(
|
|
8
|
+
state: &str,
|
|
9
|
+
proof: &TaskProofStatus,
|
|
10
|
+
findings: &[TaskStatusFinding],
|
|
11
|
+
commands: &[TaskRecommendedCommand],
|
|
12
|
+
scope: &TaskScopeStatus,
|
|
13
|
+
) -> NextActionV2 {
|
|
14
|
+
let (action_type, reason, paths, safe) = prioritized_action(state, proof, findings, scope);
|
|
15
|
+
NextActionV2 {
|
|
16
|
+
action_type: action_type.to_string(),
|
|
17
|
+
reason,
|
|
18
|
+
check_ids: action_check_ids(proof),
|
|
19
|
+
commands: commands
|
|
20
|
+
.iter()
|
|
21
|
+
.map(|command| command.command.clone())
|
|
22
|
+
.collect(),
|
|
23
|
+
paths,
|
|
24
|
+
safe_to_execute: safe,
|
|
25
|
+
requires_user_approval: !safe,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
fn prioritized_action(
|
|
30
|
+
state: &str,
|
|
31
|
+
proof: &TaskProofStatus,
|
|
32
|
+
findings: &[TaskStatusFinding],
|
|
33
|
+
scope: &TaskScopeStatus,
|
|
34
|
+
) -> (&'static str, String, Vec<String>, bool) {
|
|
35
|
+
if has_finding(findings, "task.state.conflict_markers") {
|
|
36
|
+
return (
|
|
37
|
+
"blocked",
|
|
38
|
+
"Task-state conflict markers must be resolved.".to_string(),
|
|
39
|
+
vec![],
|
|
40
|
+
false,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
if has_git_recovery(findings) {
|
|
44
|
+
return (
|
|
45
|
+
"recover_git_state",
|
|
46
|
+
"Git recovery is required before task work can continue.".to_string(),
|
|
47
|
+
vec![],
|
|
48
|
+
false,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if has_finding(findings, "task.scope.out_of_scope_change") {
|
|
52
|
+
return (
|
|
53
|
+
"repair_scope",
|
|
54
|
+
"Out-of-scope changes must be removed or admitted.".to_string(),
|
|
55
|
+
scope.out_of_scope_changed_paths.clone(),
|
|
56
|
+
true,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
if !proof.missing_checks.is_empty() || !proof.stale_checks.is_empty() {
|
|
60
|
+
return (
|
|
61
|
+
"rerun_checks",
|
|
62
|
+
"Required proof is missing or stale.".to_string(),
|
|
63
|
+
scope.in_scope_changed_paths.clone(),
|
|
64
|
+
true,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
match state {
|
|
68
|
+
"idle" | "missing" => (
|
|
69
|
+
"create_task",
|
|
70
|
+
"No active task is in progress.".to_string(),
|
|
71
|
+
vec![],
|
|
72
|
+
true,
|
|
73
|
+
),
|
|
74
|
+
"complete" => (
|
|
75
|
+
"none",
|
|
76
|
+
"Task is complete and proof is current.".to_string(),
|
|
77
|
+
vec![],
|
|
78
|
+
true,
|
|
79
|
+
),
|
|
80
|
+
_ => (
|
|
81
|
+
"continue_editing",
|
|
82
|
+
"Active task is healthy.".to_string(),
|
|
83
|
+
scope.in_scope_changed_paths.clone(),
|
|
84
|
+
true,
|
|
85
|
+
),
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
use super::super::model::{TaskProofStatus, TaskStatusFinding};
|
|
2
|
+
use super::shared::has_finding;
|
|
3
|
+
|
|
4
|
+
pub fn task_status_exit_code(findings: &[TaskStatusFinding], proof: &TaskProofStatus) -> i32 {
|
|
5
|
+
if has_finding(findings, "task.state.conflict_markers")
|
|
6
|
+
|| has_finding(findings, "task.state.invalid_json")
|
|
7
|
+
|| has_finding(findings, "task.state.active_task_missing")
|
|
8
|
+
{
|
|
9
|
+
40
|
|
10
|
+
} else if findings.iter().any(|finding| {
|
|
11
|
+
matches!(
|
|
12
|
+
finding.id.as_str(),
|
|
13
|
+
"task.git.operation_in_progress"
|
|
14
|
+
| "task.git.admission_head_missing"
|
|
15
|
+
| "task.git.admission_head_not_reachable"
|
|
16
|
+
)
|
|
17
|
+
}) {
|
|
18
|
+
30
|
|
19
|
+
} else if has_finding(findings, "task.scope.out_of_scope_change") {
|
|
20
|
+
20
|
|
21
|
+
} else if has_finding(findings, "task.state.no_active_task_with_diff") {
|
|
22
|
+
50
|
|
23
|
+
} else if has_finding(findings, "task.state.completed_task_has_diff") {
|
|
24
|
+
60
|
|
25
|
+
} else if !proof.missing_checks.is_empty() {
|
|
26
|
+
10
|
|
27
|
+
} else if !proof.stale_checks.is_empty() {
|
|
28
|
+
11
|
|
29
|
+
} else {
|
|
30
|
+
0
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
use super::super::agent_model::AgentLoop;
|
|
2
|
+
use super::super::model::{TaskProofStatus, TaskScopeStatus, TaskStatusFinding};
|
|
3
|
+
use super::shared::{action_check_ids, has_finding, has_git_recovery};
|
|
4
|
+
|
|
5
|
+
pub(in crate::task_state::status) fn agent_loop(
|
|
6
|
+
state: &str,
|
|
7
|
+
proof: &TaskProofStatus,
|
|
8
|
+
findings: &[TaskStatusFinding],
|
|
9
|
+
scope: &TaskScopeStatus,
|
|
10
|
+
) -> AgentLoop {
|
|
11
|
+
let loop_state = agent_loop_state(state, proof, findings, scope);
|
|
12
|
+
AgentLoop {
|
|
13
|
+
can_continue_editing: matches!(loop_state.as_str(), "healthy" | "ready_to_commit"),
|
|
14
|
+
can_run_checks: !matches!(
|
|
15
|
+
loop_state.as_str(),
|
|
16
|
+
"blocked_by_scope_drift" | "blocked_by_git_state" | "blocked_by_task_state"
|
|
17
|
+
),
|
|
18
|
+
can_record_proof: matches!(
|
|
19
|
+
loop_state.as_str(),
|
|
20
|
+
"blocked_by_missing_proof" | "blocked_by_stale_proof" | "ready_to_commit"
|
|
21
|
+
),
|
|
22
|
+
can_commit: loop_state == "ready_to_commit",
|
|
23
|
+
must_do_next: must_do_next(&loop_state, proof),
|
|
24
|
+
must_not_do: must_not_do(&loop_state),
|
|
25
|
+
state: loop_state,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
fn agent_loop_state(
|
|
30
|
+
state: &str,
|
|
31
|
+
proof: &TaskProofStatus,
|
|
32
|
+
findings: &[TaskStatusFinding],
|
|
33
|
+
scope: &TaskScopeStatus,
|
|
34
|
+
) -> String {
|
|
35
|
+
if has_finding(findings, "task.state.conflict_markers") {
|
|
36
|
+
"blocked_by_task_state"
|
|
37
|
+
} else if has_git_recovery(findings) {
|
|
38
|
+
"blocked_by_git_state"
|
|
39
|
+
} else if has_finding(findings, "task.scope.out_of_scope_change") {
|
|
40
|
+
"blocked_by_scope_drift"
|
|
41
|
+
} else if has_finding(findings, "task.state.no_active_task_with_diff") {
|
|
42
|
+
"blocked_by_task_state"
|
|
43
|
+
} else if !proof.missing_checks.is_empty() {
|
|
44
|
+
"blocked_by_missing_proof"
|
|
45
|
+
} else if !proof.stale_checks.is_empty() {
|
|
46
|
+
"blocked_by_stale_proof"
|
|
47
|
+
} else if matches!(state, "idle" | "missing") {
|
|
48
|
+
"ready_for_new_task"
|
|
49
|
+
} else if state == "complete" || !scope.in_scope_changed_paths.is_empty() {
|
|
50
|
+
"ready_to_commit"
|
|
51
|
+
} else {
|
|
52
|
+
"healthy"
|
|
53
|
+
}
|
|
54
|
+
.to_string()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fn must_do_next(state: &str, proof: &TaskProofStatus) -> Vec<String> {
|
|
58
|
+
match state {
|
|
59
|
+
"blocked_by_missing_proof" | "blocked_by_stale_proof" => action_check_ids(proof),
|
|
60
|
+
value => vec![value.to_string()],
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fn must_not_do(state: &str) -> Vec<String> {
|
|
65
|
+
let mut items = vec!["Do not bypass the NAOME commit gate.".to_string()];
|
|
66
|
+
if state != "ready_to_commit" {
|
|
67
|
+
items.push("Do not commit until task can-transition allows completion.".to_string());
|
|
68
|
+
}
|
|
69
|
+
items
|
|
70
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
use super::super::agent_model::PolicyHints;
|
|
2
|
+
use super::super::model::{TaskProofStatus, TaskScopeStatus, TaskStatusFinding};
|
|
3
|
+
use super::shared::action_check_ids;
|
|
4
|
+
|
|
5
|
+
pub(in crate::task_state::status) fn policy_hints(
|
|
6
|
+
scope: &TaskScopeStatus,
|
|
7
|
+
proof: &TaskProofStatus,
|
|
8
|
+
findings: &[TaskStatusFinding],
|
|
9
|
+
) -> PolicyHints {
|
|
10
|
+
let may_edit = if scope.in_scope_changed_paths.is_empty() {
|
|
11
|
+
scope.allowed_paths.clone()
|
|
12
|
+
} else {
|
|
13
|
+
scope.in_scope_changed_paths.clone()
|
|
14
|
+
};
|
|
15
|
+
let mut forbidden_actions = vec![
|
|
16
|
+
"Do not bypass the NAOME commit gate.".to_string(),
|
|
17
|
+
"Do not commit before can-transition or can-commit allows it.".to_string(),
|
|
18
|
+
];
|
|
19
|
+
if findings
|
|
20
|
+
.iter()
|
|
21
|
+
.any(|finding| finding.id == "task.scope.out_of_scope_change")
|
|
22
|
+
{
|
|
23
|
+
forbidden_actions.push("Do not keep out-of-scope changes in the task diff.".to_string());
|
|
24
|
+
}
|
|
25
|
+
PolicyHints {
|
|
26
|
+
may_edit,
|
|
27
|
+
must_inspect_before_edit: scope.out_of_scope_changed_paths.clone(),
|
|
28
|
+
must_run_after_edit: action_check_ids(proof),
|
|
29
|
+
forbidden_actions,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
use super::super::agent_model::{ProofRecording, ProofRecordingAfterSuccess};
|
|
2
|
+
use super::super::model::{TaskProofStatus, TaskScopeStatus};
|
|
3
|
+
use super::shared::action_check_ids;
|
|
4
|
+
|
|
5
|
+
pub(in crate::task_state::status) fn proof_recording(
|
|
6
|
+
task_id: Option<&str>,
|
|
7
|
+
scope: &TaskScopeStatus,
|
|
8
|
+
proof: &TaskProofStatus,
|
|
9
|
+
) -> ProofRecording {
|
|
10
|
+
let checks_to_record = action_check_ids(proof);
|
|
11
|
+
let proof_batch_id = format!(
|
|
12
|
+
"{}-proof",
|
|
13
|
+
task_id.unwrap_or("current-task").replace('_', "-")
|
|
14
|
+
);
|
|
15
|
+
ProofRecording {
|
|
16
|
+
path_set_id: "current-task-diff".to_string(),
|
|
17
|
+
paths: scope.in_scope_changed_paths.clone(),
|
|
18
|
+
proof_batch_id,
|
|
19
|
+
checks_to_record: checks_to_record.clone(),
|
|
20
|
+
after_success: ProofRecordingAfterSuccess {
|
|
21
|
+
record_proof: !checks_to_record.is_empty(),
|
|
22
|
+
instructions: "After each command exits 0, record one proof batch using pathSetId current-task-diff and the listed in-scope paths.".to_string(),
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
use super::super::agent_model::RecoveryGuidance;
|
|
2
|
+
use super::super::model::TaskStatusFinding;
|
|
3
|
+
|
|
4
|
+
pub(in crate::task_state::status) fn recovery_guidance(
|
|
5
|
+
findings: &[TaskStatusFinding],
|
|
6
|
+
) -> Vec<RecoveryGuidance> {
|
|
7
|
+
findings
|
|
8
|
+
.iter()
|
|
9
|
+
.filter(|finding| {
|
|
10
|
+
finding.id.starts_with("task.git.") || finding.id == "task.state.conflict_markers"
|
|
11
|
+
})
|
|
12
|
+
.map(|finding| RecoveryGuidance {
|
|
13
|
+
id: finding.id.clone(),
|
|
14
|
+
reason: finding.message.clone(),
|
|
15
|
+
safe_next_action: finding.suggested_fix.clone(),
|
|
16
|
+
requires_user_approval: true,
|
|
17
|
+
})
|
|
18
|
+
.collect()
|
|
19
|
+
}
|