diff --git a/README.md b/README.md index 0bc8691..2a86944 100644 --- a/README.md +++ b/README.md @@ -207,6 +207,7 @@ El núcleo no cambia; solo el adaptador. - Guía breve: `HOWTO.md` - Starter pack: `starter-pack/README.md` - Adaptación del template: `TEMPLATE.md` +- Índice de docs: `docs/README.md` - Layout del repo: `docs/repository-layout.md` - Referencia de scripts: `docs/scripts-reference.md` - Manual Skeleton (uso + mejoras): `docs/skeleton-manual.md` diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..21ff374 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,17 @@ +# Docs index + +## Core docs +- `../README.md` — framework overview +- `../HOWTO.md` — quick start +- `../HOWTO-FEATURE.md` — feature workflow +- `../TEMPLATE.md` — how to adapt ARNES + +## Reference docs +- `repository-layout.md` — repo structure and separation rules +- `scripts-reference.md` — start/verify/ticket/publish/install scripts +- `skeleton-manual.md` — default Skeleton UI notes + +## Harness source of truth +- `../harness/agents.matrix.yml` — roles and edit boundaries +- `../harness/workflow.stages.yml` — ordered workflow stages +- `../harness/policies/` — governance, security, quality, language, model routing diff --git a/docs/repository-layout.md b/docs/repository-layout.md index 3abbfd5..1a3434c 100644 --- a/docs/repository-layout.md +++ b/docs/repository-layout.md @@ -13,15 +13,17 @@ - `harness/` — workflow, roles, policies, contracts - `spec/` — product, tech, acceptance, SDD, BDD source docs - `features/` — optional executable BDD runner assets -- `scripts/` — start, verify, ticket creation, runtime status +- `scripts/` — start, verify, ticket creation, publish, install, runtime status - `platforms/` — platform adapters (pi, opencode) - `defaults/` — optional starter assets +- `tests/` — self-tests for the ARNES source repo only ## Recommended separation - Core ARNES should stay generic. - Domain checks go in `scripts/verify.local.sh`. - Domain rules go in `AGENTS.local.md`. - Real code should not be mixed into `harness/`, `work/`, `backlog/`, or `spec/`. +- Source-repo self-tests under `tests/` are not part of installed project repos. ## Default project shape ```text diff --git a/docs/scripts-reference.md b/docs/scripts-reference.md index 320d06a..2b8c023 100644 --- a/docs/scripts-reference.md +++ b/docs/scripts-reference.md @@ -14,6 +14,8 @@ Interactive bootstrap wizard. What it does: - asks project metadata - chooses default app directory (`project/` by default) +- defaults Python/Flask projects to `python3 -m unittest discover -s project/tests -v` +- seeds a minimal bootstrap smoke test under `project/tests/` for Python/Flask - writes `harness/project.config.json` - creates `scripts/verify.local.sh` - can seed one bootstrap ticket @@ -30,6 +32,7 @@ What it checks: - only one feature is `in_progress` - done features have all required artifacts, including publish evidence - runtime status JSON is valid +- source-repo self-tests run automatically if `tests/` exists - optional local overlay runs if present ## `python3 scripts/new_ticket.py` diff --git a/scripts/__pycache__/agent_status.cpython-313.pyc b/scripts/__pycache__/agent_status.cpython-313.pyc new file mode 100644 index 0000000..ee1ed32 Binary files /dev/null and b/scripts/__pycache__/agent_status.cpython-313.pyc differ diff --git a/scripts/__pycache__/new_ticket.cpython-313.pyc b/scripts/__pycache__/new_ticket.cpython-313.pyc new file mode 100644 index 0000000..3b460d1 Binary files /dev/null and b/scripts/__pycache__/new_ticket.cpython-313.pyc differ diff --git a/scripts/__pycache__/publish_ticket.cpython-313.pyc b/scripts/__pycache__/publish_ticket.cpython-313.pyc new file mode 100644 index 0000000..88ca8dc Binary files /dev/null and b/scripts/__pycache__/publish_ticket.cpython-313.pyc differ diff --git a/scripts/start.sh b/scripts/start.sh index d13b053..e508d20 100755 --- a/scripts/start.sh +++ b/scripts/start.sh @@ -38,7 +38,12 @@ else CSSFW="skeleton" fi -TEST_CMD="$(ask 'Test command' 'make test')" +DEFAULT_TEST_CMD="echo TODO-set-test-command" +if [ "$BACKEND" = "python/flask" ]; then + DEFAULT_TEST_CMD="python3 -m unittest discover -s $APP_DIR/tests -v" +fi + +TEST_CMD="$(ask 'Test command' "$DEFAULT_TEST_CMD")" LINT_CMD="$(ask 'Lint command (optional)' '')" MODEL_MODE="$(ask 'Model mode (lean/balanced/power)' 'lean')" ADD_BOOTSTRAP="$(ask 'Create bootstrap ticket F-001 now? (y/n)' 'y')" @@ -52,7 +57,19 @@ Configured by ARNES start wizard. EOF if [ "$BACKEND" = "python/flask" ]; then - mkdir -p "$APP_DIR/templates" "$APP_DIR/static/js" "$APP_DIR/static/css" "$APP_DIR/static/images" + mkdir -p "$APP_DIR/templates" "$APP_DIR/static/js" "$APP_DIR/static/css" "$APP_DIR/static/images" "$APP_DIR/tests" + [ -f "$APP_DIR/tests/test_bootstrap.py" ] || cat > "$APP_DIR/tests/test_bootstrap.py" <<'PY' +import unittest + + +class BootstrapSmokeTest(unittest.TestCase): + def test_bootstrap(self): + self.assertTrue(True) + + +if __name__ == '__main__': + unittest.main() +PY fi if [ "$CSSFW" = "skeleton" ]; then @@ -195,3 +212,4 @@ echo "- Ticket tool: python3 scripts/new_ticket.py" echo "- Publish tool: python3 scripts/publish_ticket.py --feature-id F-001" echo "- Verify: ./scripts/verify.sh" echo "- Runtime: python3 scripts/agent_status.py show" +echo "- Reminder: configure git remote before final publish if missing" diff --git a/scripts/verify.sh b/scripts/verify.sh index 4e2d87a..8586534 100755 --- a/scripts/verify.sh +++ b/scripts/verify.sh @@ -21,6 +21,7 @@ required=( "README.md" "HOWTO.md" "TEMPLATE.md" + "docs/README.md" "docs/repository-layout.md" "docs/scripts-reference.md" "harness/agents.matrix.yml" diff --git a/tests/__pycache__/test_arnes_core.cpython-313.pyc b/tests/__pycache__/test_arnes_core.cpython-313.pyc new file mode 100644 index 0000000..ca8895a Binary files /dev/null and b/tests/__pycache__/test_arnes_core.cpython-313.pyc differ diff --git a/tests/test_arnes_core.py b/tests/test_arnes_core.py new file mode 100644 index 0000000..a19a176 --- /dev/null +++ b/tests/test_arnes_core.py @@ -0,0 +1,112 @@ +import json +import subprocess +import tempfile +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +INSTALL = ROOT / 'scripts' / 'install_into_repo.sh' + + +class ArnesCoreTests(unittest.TestCase): + def run_cmd(self, args, cwd=None, input_text=None, check=True): + result = subprocess.run( + args, + cwd=cwd or ROOT, + input=input_text, + text=True, + capture_output=True, + ) + if check and result.returncode != 0: + raise AssertionError( + f"Command failed: {args}\nSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}" + ) + return result + + def test_install_refuses_source_repo(self): + result = self.run_cmd([str(INSTALL), str(ROOT)], check=False) + self.assertNotEqual(result.returncode, 0) + self.assertIn('Refusing to install ARNES into its own source repository', result.stdout) + + def test_install_start_verify_and_publish_in_external_repo(self): + with tempfile.TemporaryDirectory() as tmp: + tmp_path = Path(tmp) + target = tmp_path / 'project-repo' + remote = tmp_path / 'remote.git' + + self.run_cmd([str(INSTALL), str(target)]) + self.assertTrue((target / 'scripts' / 'start.sh').is_file()) + self.assertFalse((target / 'tests').exists(), 'source self-tests must not be installed into project repo') + + self.run_cmd(['git', 'config', 'user.name', 'ARNES Bot'], cwd=target) + self.run_cmd(['git', 'config', 'user.email', 'arnes@example.com'], cwd=target) + self.run_cmd(['git', 'init', '--bare', str(remote)]) + self.run_cmd(['git', 'remote', 'add', 'origin', str(remote)], cwd=target) + + wizard_input = '\n'.join([ + 'demo-project', + 'Demo project', + 'project', + '1', + '', + '', + '', + '', + ]) + '\n' + self.run_cmd(['./scripts/start.sh'], cwd=target, input_text=wizard_input) + + self.assertTrue((target / 'project' / 'tests').is_dir()) + cfg = json.loads((target / 'harness' / 'project.config.json').read_text(encoding='utf-8')) + self.assertEqual(cfg['app_dir'], 'project') + self.assertEqual(cfg['commands']['test'], 'python3 -m unittest discover -s project/tests -v') + + verify_result = self.run_cmd(['./scripts/verify.sh'], cwd=target) + self.assertIn('Harness verificado', verify_result.stdout) + + self.run_cmd(['python3', 'scripts/new_ticket.py'], cwd=target, input_text='\n'.join([ + 'fix', + 'Repair docs', + 'Need docs repair', + 'Make docs clear', + 'Docs flow', + 'No redesign', + 'low', + 'med', + 'Docs clear', + 'Verify green', + '', + ]) + '\n') + + with (target / 'project' / 'README.md').open('a', encoding='utf-8') as fh: + fh.write('\nchange\n') + + publish_result = self.run_cmd( + ['python3', 'scripts/publish_ticket.py', '--feature-id', 'F-001'], + cwd=target, + ) + self.assertIn('done ->', publish_result.stdout) + + publish_path = target / 'work' / 'artifacts' / 'F-001' / 'publish.json' + payload = json.loads(publish_path.read_text(encoding='utf-8')) + self.assertEqual(payload['verdict'], 'PUBLISHED') + self.assertTrue(payload['pushed']) + self.assertEqual(payload['remote'], 'origin') + + status_result = self.run_cmd(['git', 'status', '--short'], cwd=target) + self.assertEqual(status_result.stdout.strip(), '') + + remote_refs = self.run_cmd(['git', 'ls-remote', 'origin'], cwd=target) + self.assertIn('refs/heads/master', remote_refs.stdout) + + def test_agent_status_rejects_invalid_agent(self): + result = self.run_cmd( + ['python3', 'scripts/agent_status.py', 'set', '--agent', 'nope'], + check=False, + ) + self.assertNotEqual(result.returncode, 0) + combined = (result.stdout + result.stderr) + self.assertIn('Invalid agent: nope', combined) + + +if __name__ == '__main__': + unittest.main()