objective: remove one-shot import
This commit is contained in:
parent
e653f8f08b
commit
0eb840ab4b
|
|
@ -17,8 +17,8 @@ const DETAIL_BODY_LIMIT: usize = 64 * 1024;
|
|||
/// Workspace-scoped runtime authority for project resources.
|
||||
///
|
||||
/// Normal Backend API handlers should depend on this authority abstraction, not
|
||||
/// on legacy filesystem layouts. Filesystem readers belong in explicit import
|
||||
/// / migration helpers such as `objective_import`.
|
||||
/// on legacy filesystem layouts. Filesystem readers belong in explicitly
|
||||
/// temporary migration/repair tools and tests, not normal runtime authority paths.
|
||||
pub trait WorkspaceAuthority: ObjectiveAuthority + TicketAuthority + MemoryAuthorityMarker {}
|
||||
|
||||
impl<T> WorkspaceAuthority for T where
|
||||
|
|
@ -216,7 +216,7 @@ mod tests {
|
|||
use std::path::Path;
|
||||
|
||||
use super::*;
|
||||
use crate::store::WorkspaceRecord;
|
||||
use crate::store::{ObjectiveRecord, ObjectiveTicketLinkRecord, WorkspaceRecord};
|
||||
|
||||
#[tokio::test]
|
||||
async fn sqlite_workspace_authority_reads_sqlite_records_without_filesystem_authority() {
|
||||
|
|
@ -228,7 +228,6 @@ mod tests {
|
|||
dir.path().join(".yoi/tickets"),
|
||||
))
|
||||
.unwrap();
|
||||
write_objective(dir.path(), "00000000001J3", "Control plane", "active");
|
||||
let store = SqliteWorkspaceStore::open(&db_path).unwrap();
|
||||
store
|
||||
.upsert_workspace(&WorkspaceRecord {
|
||||
|
|
@ -241,12 +240,30 @@ mod tests {
|
|||
})
|
||||
.await
|
||||
.unwrap();
|
||||
crate::objective_import::import_legacy_objectives_and_memory_staging(
|
||||
dir.path(),
|
||||
"workspace-test",
|
||||
&store,
|
||||
)
|
||||
.unwrap();
|
||||
store
|
||||
.upsert_objective(&ObjectiveRecord {
|
||||
workspace_id: "workspace-test".to_string(),
|
||||
objective_id: "00000000001J3".to_string(),
|
||||
title: "Control plane".to_string(),
|
||||
state: "active".to_string(),
|
||||
body_md: "Objective body.\n".to_string(),
|
||||
created_at: "2026-01-01T00:00:00Z".to_string(),
|
||||
updated_at: "2026-01-02T00:00:00Z".to_string(),
|
||||
})
|
||||
.unwrap();
|
||||
store
|
||||
.replace_objective_ticket_links(
|
||||
"workspace-test",
|
||||
"00000000001J3",
|
||||
&[ObjectiveTicketLinkRecord {
|
||||
workspace_id: "workspace-test".to_string(),
|
||||
objective_id: "00000000001J3".to_string(),
|
||||
ticket_id: "00000000001J2".to_string(),
|
||||
kind: "linked".to_string(),
|
||||
created_at: "2026-01-01T00:00:00Z".to_string(),
|
||||
}],
|
||||
)
|
||||
.unwrap();
|
||||
write_objective(
|
||||
dir.path(),
|
||||
"00000000001J4",
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ pub mod config;
|
|||
pub mod hosts;
|
||||
pub mod identity;
|
||||
pub mod memory_staging;
|
||||
pub mod objective_import;
|
||||
pub mod observation;
|
||||
pub mod profile_settings;
|
||||
pub mod records;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ enum Command {
|
|||
Init(InitOptions),
|
||||
ConfigDefault,
|
||||
ConfigDiff(WorkspacePathOptions),
|
||||
ImportObjectives(ImportObjectivesOptions),
|
||||
Skills(SkillsCommand),
|
||||
Help,
|
||||
}
|
||||
|
|
@ -37,12 +36,6 @@ struct WorkspacePathOptions {
|
|||
workspace: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ImportObjectivesOptions {
|
||||
workspace: PathBuf,
|
||||
database: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum SkillsCommand {
|
||||
List(WorkspacePathOptions),
|
||||
|
|
@ -79,7 +72,6 @@ async fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
Command::Init(options) => run_init(options).await,
|
||||
Command::ConfigDefault => run_config_default(),
|
||||
Command::ConfigDiff(options) => run_config_diff(options),
|
||||
Command::ImportObjectives(options) => run_import_objectives(options).await,
|
||||
Command::Skills(command) => run_skills(command),
|
||||
Command::Help => Ok(()),
|
||||
}
|
||||
|
|
@ -100,15 +92,6 @@ fn parse_command(args: &[String]) -> Result<Command, CliError> {
|
|||
Ok(Command::Init(parse_init_options(rest)?))
|
||||
}
|
||||
"config" => parse_config_command(rest),
|
||||
"import-objectives" => {
|
||||
if rest.iter().any(|arg| arg == "--help" || arg == "-h") {
|
||||
print_import_objectives_help();
|
||||
return Ok(Command::Help);
|
||||
}
|
||||
Ok(Command::ImportObjectives(parse_import_objectives_options(
|
||||
rest,
|
||||
)?))
|
||||
}
|
||||
"skills" => parse_skills_command(rest),
|
||||
"serve" => {
|
||||
if rest.iter().any(|arg| arg == "--help" || arg == "-h") {
|
||||
|
|
@ -122,7 +105,7 @@ fn parse_command(args: &[String]) -> Result<Command, CliError> {
|
|||
Ok(Command::Help)
|
||||
}
|
||||
other => Err(CliError(format!(
|
||||
"unknown command `{other}`; expected `init`, `config`, `import-objectives`, `skills`, or `serve`"
|
||||
"unknown command `{other}`; expected `init`, `config`, `skills`, or `serve`"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
|
@ -186,45 +169,6 @@ fn run_config_diff(options: WorkspacePathOptions) -> Result<(), Box<dyn std::err
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_import_objectives(
|
||||
options: ImportObjectivesOptions,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let identity = WorkspaceIdentity::load_required(&options.workspace)?;
|
||||
let database_path = options
|
||||
.database
|
||||
.unwrap_or_else(ServerConfig::default_server_database_path);
|
||||
let store = SqliteWorkspaceStore::open(&database_path)?;
|
||||
store
|
||||
.upsert_workspace(&WorkspaceRecord {
|
||||
workspace_id: identity.workspace_id.clone(),
|
||||
owner_account_id: None,
|
||||
display_name: identity.display_name.clone(),
|
||||
state: "active".to_string(),
|
||||
created_at: identity.created_at.clone(),
|
||||
updated_at: chrono::Utc::now().to_rfc3339(),
|
||||
})
|
||||
.await?;
|
||||
let report =
|
||||
yoi_workspace_server::objective_import::import_legacy_objectives_and_memory_staging(
|
||||
&options.workspace,
|
||||
&identity.workspace_id,
|
||||
&store,
|
||||
)?;
|
||||
println!(
|
||||
"imported objectives={} resources={} ticket_links={} skipped_ticket_links={} memory_staging={} invalid_records={}",
|
||||
report.objectives_imported,
|
||||
report.objective_resources_imported,
|
||||
report.objective_ticket_links_imported,
|
||||
report.objective_ticket_links_skipped,
|
||||
report.memory_staging_records_imported,
|
||||
report.invalid_records.len(),
|
||||
);
|
||||
for invalid in report.invalid_records {
|
||||
eprintln!("invalid objective record: {invalid}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_skills(command: SkillsCommand) -> Result<(), Box<dyn std::error::Error>> {
|
||||
match command {
|
||||
SkillsCommand::List(options) => {
|
||||
|
|
@ -433,47 +377,6 @@ fn parse_workspace_path_options(args: &[String]) -> Result<WorkspacePathOptions,
|
|||
Ok(WorkspacePathOptions { workspace })
|
||||
}
|
||||
|
||||
fn parse_import_objectives_options(args: &[String]) -> Result<ImportObjectivesOptions, CliError> {
|
||||
let mut workspace = std::env::current_dir()
|
||||
.map_err(|error| CliError(format!("failed to read current dir: {error}")))?;
|
||||
let mut database = None;
|
||||
let mut iter = args.iter();
|
||||
while let Some(arg) = iter.next() {
|
||||
match arg.as_str() {
|
||||
"--workspace" => {
|
||||
let value = iter
|
||||
.next()
|
||||
.ok_or_else(|| CliError("--workspace requires a path".to_string()))?;
|
||||
workspace = PathBuf::from(value);
|
||||
}
|
||||
value if value.starts_with("--workspace=") => {
|
||||
workspace = PathBuf::from(value_after_equals(arg, "--workspace")?);
|
||||
}
|
||||
"--database" => {
|
||||
let value = iter
|
||||
.next()
|
||||
.ok_or_else(|| CliError("--database requires a path".to_string()))?;
|
||||
database = Some(PathBuf::from(value));
|
||||
}
|
||||
value if value.starts_with("--database=") => {
|
||||
database = Some(PathBuf::from(value_after_equals(arg, "--database")?));
|
||||
}
|
||||
other => {
|
||||
return Err(CliError(format!(
|
||||
"unknown import-objectives option `{other}`"
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
let workspace = workspace
|
||||
.canonicalize()
|
||||
.map_err(|error| CliError(format!("failed to canonicalize workspace: {error}")))?;
|
||||
Ok(ImportObjectivesOptions {
|
||||
workspace,
|
||||
database,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_init_options(args: &[String]) -> Result<InitOptions, CliError> {
|
||||
let mut workspace = std::env::current_dir()
|
||||
.map_err(|error| CliError(format!("failed to read current dir: {error}")))?;
|
||||
|
|
@ -550,7 +453,7 @@ fn parse_listen(value: &str) -> Result<SocketAddr, CliError> {
|
|||
|
||||
fn print_help() {
|
||||
println!(
|
||||
"yoi-workspace-server\n\nUsage:\n yoi-workspace-server init [OPTIONS]\n yoi-workspace-server config <COMMAND> [OPTIONS]\n yoi-workspace-server import-objectives [OPTIONS]\n yoi-workspace-server skills <COMMAND> [OPTIONS]\n yoi-workspace-server serve [OPTIONS]\n\nOptions:\n -h, --help Print help"
|
||||
"yoi-workspace-server\n\nUsage:\n yoi-workspace-server init [OPTIONS]\n yoi-workspace-server config <COMMAND> [OPTIONS]\n yoi-workspace-server skills <COMMAND> [OPTIONS]\n yoi-workspace-server serve [OPTIONS]\n\nOptions:\n -h, --help Print help"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -566,12 +469,6 @@ fn print_config_help() {
|
|||
);
|
||||
}
|
||||
|
||||
fn print_import_objectives_help() {
|
||||
println!(
|
||||
"yoi-workspace-server import-objectives\n\nUsage:\n yoi-workspace-server import-objectives [OPTIONS]\n\nDescription:\n Imports legacy .yoi/objectives records and .yoi/memory/_staging JSON files into the Workspace SQLite authority.\n\nOptions:\n --workspace <PATH> Workspace root (defaults to cwd)\n --database <PATH> Server SQLite DB path (defaults to canonical server DB)\n -h, --help Print help"
|
||||
);
|
||||
}
|
||||
|
||||
fn print_skills_help() {
|
||||
println!(
|
||||
"yoi-workspace-server skills\n\nUsage:\n yoi-workspace-server skills list [OPTIONS]\n yoi-workspace-server skills lint [OPTIONS]\n yoi-workspace-server skills show <NAME> [OPTIONS]\n\nDescription:\n Uses the Workspace backend Skill catalog/lint/detail authority. Catalog output is lightweight and omits full SKILL.md bodies; detail output includes the body. allowed-tools and scripts are diagnostics only.\n\nOptions:\n --workspace <PATH> Workspace root (defaults to cwd)\n -h, --help Print help"
|
||||
|
|
|
|||
|
|
@ -1,260 +0,0 @@
|
|||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use chrono::Utc;
|
||||
use project_record::validate_record_id;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::store::{
|
||||
ControlPlaneStore, MemoryStagingRecord, ObjectiveRecord, ObjectiveResourceRecord,
|
||||
ObjectiveTicketLinkRecord,
|
||||
};
|
||||
use crate::{Error, Result};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ObjectiveImportReport {
|
||||
pub objectives_imported: usize,
|
||||
pub objective_resources_imported: usize,
|
||||
pub objective_ticket_links_imported: usize,
|
||||
pub objective_ticket_links_skipped: usize,
|
||||
pub memory_staging_records_imported: usize,
|
||||
pub invalid_records: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ObjectiveFrontmatter {
|
||||
title: String,
|
||||
state: String,
|
||||
#[serde(default)]
|
||||
created_at: Option<String>,
|
||||
#[serde(default)]
|
||||
updated_at: Option<String>,
|
||||
#[serde(default)]
|
||||
linked_tickets: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn import_legacy_objectives_and_memory_staging<S: ControlPlaneStore>(
|
||||
workspace_root: &Path,
|
||||
workspace_id: &str,
|
||||
store: &S,
|
||||
) -> Result<ObjectiveImportReport> {
|
||||
let mut report = ObjectiveImportReport {
|
||||
objectives_imported: 0,
|
||||
objective_resources_imported: 0,
|
||||
objective_ticket_links_imported: 0,
|
||||
objective_ticket_links_skipped: 0,
|
||||
memory_staging_records_imported: 0,
|
||||
invalid_records: Vec::new(),
|
||||
};
|
||||
|
||||
import_objectives(workspace_root, workspace_id, store, &mut report)?;
|
||||
import_memory_staging(workspace_root, workspace_id, store, &mut report)?;
|
||||
Ok(report)
|
||||
}
|
||||
|
||||
fn import_objectives<S: ControlPlaneStore>(
|
||||
workspace_root: &Path,
|
||||
workspace_id: &str,
|
||||
store: &S,
|
||||
report: &mut ObjectiveImportReport,
|
||||
) -> Result<()> {
|
||||
let root = workspace_root.join(".yoi/objectives");
|
||||
if !root.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
for entry in fs::read_dir(root)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if !path.is_dir() {
|
||||
continue;
|
||||
}
|
||||
let objective_id = entry.file_name().to_string_lossy().to_string();
|
||||
if let Err(err) = validate_record_id(&objective_id) {
|
||||
report
|
||||
.invalid_records
|
||||
.push(format!("{objective_id}: invalid objective id: {err}"));
|
||||
continue;
|
||||
}
|
||||
match import_one_objective(&path, workspace_id, &objective_id, store) {
|
||||
Ok(item) => {
|
||||
report.objectives_imported += 1;
|
||||
report.objective_resources_imported += item.resources;
|
||||
report.objective_ticket_links_imported += item.links;
|
||||
report.objective_ticket_links_skipped += item.skipped_links;
|
||||
}
|
||||
Err(err) => report
|
||||
.invalid_records
|
||||
.push(format!("{objective_id}: {err}")),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct ImportedObjectiveCounts {
|
||||
resources: usize,
|
||||
links: usize,
|
||||
skipped_links: usize,
|
||||
}
|
||||
|
||||
fn import_one_objective<S: ControlPlaneStore>(
|
||||
objective_dir: &Path,
|
||||
workspace_id: &str,
|
||||
objective_id: &str,
|
||||
store: &S,
|
||||
) -> Result<ImportedObjectiveCounts> {
|
||||
let item_path = objective_dir.join("item.md");
|
||||
let raw = fs::read_to_string(&item_path)?;
|
||||
let (frontmatter, body) = split_frontmatter(&raw, objective_id)?;
|
||||
let meta: ObjectiveFrontmatter = serde_yaml::from_str(frontmatter)?;
|
||||
let now = Utc::now().to_rfc3339();
|
||||
let created_at = meta.created_at.clone().unwrap_or_else(|| now.clone());
|
||||
let updated_at = meta.updated_at.clone().unwrap_or_else(|| now.clone());
|
||||
|
||||
store.upsert_objective(&ObjectiveRecord {
|
||||
workspace_id: workspace_id.to_string(),
|
||||
objective_id: objective_id.to_string(),
|
||||
title: meta.title,
|
||||
state: meta.state,
|
||||
body_md: body.trim_start().to_string(),
|
||||
created_at: created_at.clone(),
|
||||
updated_at: updated_at.clone(),
|
||||
})?;
|
||||
|
||||
let mut links = Vec::new();
|
||||
let mut skipped_links = 0;
|
||||
for ticket_id in meta.linked_tickets {
|
||||
if validate_record_id(&ticket_id).is_ok() {
|
||||
links.push(ObjectiveTicketLinkRecord {
|
||||
workspace_id: workspace_id.to_string(),
|
||||
objective_id: objective_id.to_string(),
|
||||
ticket_id,
|
||||
kind: "linked".to_string(),
|
||||
created_at: now.clone(),
|
||||
});
|
||||
} else {
|
||||
skipped_links += 1;
|
||||
}
|
||||
}
|
||||
let imported_links = links.len();
|
||||
let links_imported = if let Err(err) =
|
||||
store.replace_objective_ticket_links(workspace_id, objective_id, &links)
|
||||
{
|
||||
// The legacy filesystem allowed links to tickets that may no longer
|
||||
// exist. Preserve the Objective itself and make the skipped count clear
|
||||
// rather than failing the whole import on a foreign-key mismatch.
|
||||
skipped_links += imported_links;
|
||||
eprintln!(
|
||||
"warning: skipped {imported_links} ticket link(s) for objective {objective_id}: {err}"
|
||||
);
|
||||
0
|
||||
} else {
|
||||
imported_links
|
||||
};
|
||||
|
||||
let mut resources = 0;
|
||||
for file in walk_files(objective_dir)? {
|
||||
let relative = file
|
||||
.strip_prefix(objective_dir)
|
||||
.map_err(|err| Error::Store(err.to_string()))?
|
||||
.to_string_lossy()
|
||||
.replace('\\', "/");
|
||||
if relative == "item.md" || relative.starts_with("_staging/") {
|
||||
continue;
|
||||
}
|
||||
let Ok(body) = fs::read_to_string(&file) else {
|
||||
continue;
|
||||
};
|
||||
store.upsert_objective_resource(&ObjectiveResourceRecord {
|
||||
workspace_id: workspace_id.to_string(),
|
||||
objective_id: objective_id.to_string(),
|
||||
resource_path: relative,
|
||||
body,
|
||||
media_type: Some("text/markdown".to_string()),
|
||||
created_at: created_at.clone(),
|
||||
updated_at: updated_at.clone(),
|
||||
})?;
|
||||
resources += 1;
|
||||
}
|
||||
|
||||
Ok(ImportedObjectiveCounts {
|
||||
resources,
|
||||
links: links_imported,
|
||||
skipped_links,
|
||||
})
|
||||
}
|
||||
|
||||
fn import_memory_staging<S: ControlPlaneStore>(
|
||||
workspace_root: &Path,
|
||||
workspace_id: &str,
|
||||
store: &S,
|
||||
report: &mut ObjectiveImportReport,
|
||||
) -> Result<()> {
|
||||
let root = workspace_root.join(".yoi/memory/_staging");
|
||||
if !root.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
let now = Utc::now().to_rfc3339();
|
||||
for entry in fs::read_dir(root)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.extension().and_then(|ext| ext.to_str()) != Some("json") {
|
||||
continue;
|
||||
}
|
||||
let raw_json = fs::read_to_string(&path)?;
|
||||
let candidate_id = candidate_id_from_json(&raw_json)
|
||||
.or_else(|| {
|
||||
path.file_stem()
|
||||
.map(|stem| stem.to_string_lossy().to_string())
|
||||
})
|
||||
.ok_or_else(|| Error::Store(format!("missing candidate id: {}", path.display())))?;
|
||||
store.upsert_memory_staging_record(&MemoryStagingRecord {
|
||||
workspace_id: workspace_id.to_string(),
|
||||
candidate_id,
|
||||
raw_json,
|
||||
source_path: Some(path.to_string_lossy().to_string()),
|
||||
imported_at: now.clone(),
|
||||
})?;
|
||||
report.memory_staging_records_imported += 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn candidate_id_from_json(raw_json: &str) -> Option<String> {
|
||||
serde_json::from_str::<serde_json::Value>(raw_json)
|
||||
.ok()
|
||||
.and_then(|value| {
|
||||
value
|
||||
.get("id")
|
||||
.and_then(|id| id.as_str())
|
||||
.map(str::to_string)
|
||||
})
|
||||
}
|
||||
|
||||
fn split_frontmatter<'a>(raw: &'a str, label: &str) -> Result<(&'a str, &'a str)> {
|
||||
let rest = raw
|
||||
.strip_prefix("---\n")
|
||||
.ok_or_else(|| Error::MissingFrontmatter(label.to_string()))?;
|
||||
let Some((frontmatter, body)) = rest.split_once("\n---\n") else {
|
||||
return Err(Error::MissingFrontmatter(label.to_string()));
|
||||
};
|
||||
Ok((frontmatter, body))
|
||||
}
|
||||
|
||||
fn walk_files(root: &Path) -> Result<Vec<PathBuf>> {
|
||||
let mut files = Vec::new();
|
||||
walk_files_inner(root, &mut files)?;
|
||||
Ok(files)
|
||||
}
|
||||
|
||||
fn walk_files_inner(path: &Path, files: &mut Vec<PathBuf>) -> Result<()> {
|
||||
for entry in fs::read_dir(path)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
walk_files_inner(&path, files)?;
|
||||
} else if path.is_file() {
|
||||
files.push(path);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -6607,7 +6607,10 @@ mod tests {
|
|||
TicketWorkerRole, WorkerInputKind, WorkerOperationState, WorkerSpawnAcceptanceRequirement,
|
||||
WorkerSpawnIntent,
|
||||
};
|
||||
use crate::store::SqliteWorkspaceStore;
|
||||
use crate::store::{
|
||||
MemoryStagingRecord, ObjectiveRecord, ObjectiveResourceRecord, ObjectiveTicketLinkRecord,
|
||||
SqliteWorkspaceStore,
|
||||
};
|
||||
|
||||
const TEST_WORKSPACE_ID: &str = "0192f0e8-4d84-7d6e-a000-000000000001";
|
||||
const TEST_REPOSITORY_ID: &str = "main";
|
||||
|
|
@ -8309,8 +8312,6 @@ mod tests {
|
|||
#[tokio::test]
|
||||
async fn serves_bounded_read_apis_and_static_spa_separately() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
write_objective(dir.path(), "00000000001J3", "API Objective", "active");
|
||||
write_memory_staging(dir.path(), "00000000001J4");
|
||||
let static_dir = dir.path().join("static");
|
||||
std::fs::create_dir_all(static_dir.join("assets")).unwrap();
|
||||
std::fs::write(static_dir.join("index.html"), "<main>Yoi Workspace</main>").unwrap();
|
||||
|
|
@ -8336,12 +8337,50 @@ mod tests {
|
|||
})
|
||||
.await
|
||||
.unwrap();
|
||||
crate::objective_import::import_legacy_objectives_and_memory_staging(
|
||||
dir.path(),
|
||||
TEST_WORKSPACE_ID,
|
||||
&sqlite_store,
|
||||
)
|
||||
.unwrap();
|
||||
sqlite_store
|
||||
.upsert_objective(&ObjectiveRecord {
|
||||
workspace_id: TEST_WORKSPACE_ID.to_string(),
|
||||
objective_id: "00000000001J3".to_string(),
|
||||
title: "API Objective".to_string(),
|
||||
state: "active".to_string(),
|
||||
body_md: "Objective body.\n".to_string(),
|
||||
created_at: "2026-01-01T00:00:00Z".to_string(),
|
||||
updated_at: "2026-01-02T00:00:00Z".to_string(),
|
||||
})
|
||||
.unwrap();
|
||||
sqlite_store
|
||||
.replace_objective_ticket_links(
|
||||
TEST_WORKSPACE_ID,
|
||||
"00000000001J3",
|
||||
&[ObjectiveTicketLinkRecord {
|
||||
workspace_id: TEST_WORKSPACE_ID.to_string(),
|
||||
objective_id: "00000000001J3".to_string(),
|
||||
ticket_id: "00000000001J2".to_string(),
|
||||
kind: "linked".to_string(),
|
||||
created_at: "2026-01-01T00:00:00Z".to_string(),
|
||||
}],
|
||||
)
|
||||
.unwrap();
|
||||
sqlite_store
|
||||
.upsert_objective_resource(&ObjectiveResourceRecord {
|
||||
workspace_id: TEST_WORKSPACE_ID.to_string(),
|
||||
objective_id: "00000000001J3".to_string(),
|
||||
resource_path: "memory-architecture-overview.md".to_string(),
|
||||
body: "# Memory architecture\n\nResource body.\n".to_string(),
|
||||
media_type: Some("text/markdown".to_string()),
|
||||
created_at: "2026-01-01T00:00:00Z".to_string(),
|
||||
updated_at: "2026-01-02T00:00:00Z".to_string(),
|
||||
})
|
||||
.unwrap();
|
||||
sqlite_store
|
||||
.upsert_memory_staging_record(&MemoryStagingRecord {
|
||||
workspace_id: TEST_WORKSPACE_ID.to_string(),
|
||||
candidate_id: "00000000001J4".to_string(),
|
||||
raw_json: r#"{"id":"00000000001J4","kind":"working_assumption"}"#.to_string(),
|
||||
source_path: None,
|
||||
imported_at: "2026-01-01T00:00:00Z".to_string(),
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
sqlite_store
|
||||
.count_memory_staging_records(TEST_WORKSPACE_ID)
|
||||
|
|
@ -9363,25 +9402,6 @@ mod tests {
|
|||
backend.create(input).unwrap();
|
||||
}
|
||||
|
||||
fn write_memory_staging(root: &Path, candidate_id: &str) {
|
||||
let staging_dir = root.join(".yoi/memory/_staging");
|
||||
std::fs::create_dir_all(&staging_dir).unwrap();
|
||||
std::fs::write(
|
||||
staging_dir.join(format!("{candidate_id}.json")),
|
||||
format!(
|
||||
r#"{{
|
||||
"schema_version": 2,
|
||||
"id": "{candidate_id}",
|
||||
"kind": "working_assumption",
|
||||
"claim": "Objective staging import test",
|
||||
"why_useful": "Verifies SQLite push"
|
||||
}}
|
||||
"#,
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn write_objective(root: &Path, id: &str, title: &str, state: &str) {
|
||||
let objective_dir = root.join(".yoi/objectives").join(id);
|
||||
std::fs::create_dir_all(&objective_dir).unwrap();
|
||||
|
|
|
|||
|
|
@ -1,66 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: scripts/import-objectives-and-staging-to-sqlite-20260726.sh [--workspace PATH] [--database PATH] [--apply]
|
||||
|
||||
Imports legacy .yoi/objectives records and .yoi/memory/_staging JSON files into
|
||||
Workspace SQLite authority by invoking yoi-workspace-server import-objectives.
|
||||
|
||||
By default this is a dry-run of the command line only. Pass --apply to execute.
|
||||
EOF
|
||||
}
|
||||
|
||||
workspace="$(pwd)"
|
||||
database="${YOI_SERVER_DB:-$HOME/.local/share/yoi/server/server.db}"
|
||||
apply=0
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--workspace)
|
||||
workspace="$2"
|
||||
shift 2
|
||||
;;
|
||||
--workspace=*)
|
||||
workspace="${1#--workspace=}"
|
||||
shift
|
||||
;;
|
||||
--database)
|
||||
database="$2"
|
||||
shift 2
|
||||
;;
|
||||
--database=*)
|
||||
database="${1#--database=}"
|
||||
shift
|
||||
;;
|
||||
--apply)
|
||||
apply=1
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "unknown argument: $1" >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
cmd=(cargo run -q -p yoi-workspace-server -- import-objectives --workspace "$workspace" --database "$database")
|
||||
|
||||
printf 'Workspace: %s\n' "$workspace"
|
||||
printf 'Database: %s\n' "$database"
|
||||
printf 'Command: '
|
||||
printf '%q ' "${cmd[@]}"
|
||||
printf '\n'
|
||||
|
||||
if [[ "$apply" -ne 1 ]]; then
|
||||
echo 'Dry run only. Re-run with --apply to import.'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
"${cmd[@]}"
|
||||
Loading…
Reference in New Issue
Block a user