mirror of
https://github.com/khodges42/nightShift.git
synced 2026-06-14 10:08:37 +00:00
Clean up some files and add a new tutorial
This commit is contained in:
parent
71e65a1df4
commit
5e5cd184b9
|
|
@ -1,84 +0,0 @@
|
|||
# Project Context Chart
|
||||
|
||||
## Entry Points
|
||||
|
||||
- `tests/test_lisp.py`: class SmokeTests(unittest.TestCase):
|
||||
|
||||
## Tests
|
||||
|
||||
- `tests/__init__.py`
|
||||
- `tests/test_lisp.py`
|
||||
|
||||
## Files
|
||||
|
||||
### `agents/implementer.md`
|
||||
|
||||
- Responsibility: You are the implementation agent for NightShift.
|
||||
- Entry point: false
|
||||
- Test file: false
|
||||
- Functions: None detected
|
||||
- Classes: None detected
|
||||
- Anchors/search terms: None detected
|
||||
|
||||
### `agents/planner.md`
|
||||
|
||||
- Responsibility: You are the planning agent for NightShift.
|
||||
- Entry point: false
|
||||
- Test file: false
|
||||
- Functions: None detected
|
||||
- Classes: None detected
|
||||
- Anchors/search terms: None detected
|
||||
|
||||
### `agents/reviewer.md`
|
||||
|
||||
- Responsibility: You are the review agent for NightShift.
|
||||
- Entry point: false
|
||||
- Test file: false
|
||||
- Functions: None detected
|
||||
- Classes: None detected
|
||||
- Anchors/search terms: None detected
|
||||
|
||||
### `lisp.py`
|
||||
|
||||
- Responsibility: """Tiny Lisp quickstart target.
|
||||
- Entry point: false
|
||||
- Test file: false
|
||||
- Functions: None detected
|
||||
- Classes: None detected
|
||||
- Anchors/search terms: None detected
|
||||
|
||||
### `nightshift.yaml`
|
||||
|
||||
- Responsibility: project:
|
||||
- Entry point: false
|
||||
- Test file: false
|
||||
- Functions: None detected
|
||||
- Classes: None detected
|
||||
- Anchors/search terms: None detected
|
||||
|
||||
### `tasks.md`
|
||||
|
||||
- Responsibility: Tasks
|
||||
- Entry point: false
|
||||
- Test file: false
|
||||
- Functions: None detected
|
||||
- Classes: None detected
|
||||
- Anchors/search terms: None detected
|
||||
|
||||
### `tests/__init__.py`
|
||||
|
||||
- Responsibility: """Quickstart Lisp test suite."""
|
||||
- Entry point: false
|
||||
- Test file: true
|
||||
- Functions: None detected
|
||||
- Classes: None detected
|
||||
- Anchors/search terms: None detected
|
||||
|
||||
### `tests/test_lisp.py`
|
||||
|
||||
- Responsibility: class SmokeTests(unittest.TestCase):
|
||||
- Entry point: true
|
||||
- Test file: true
|
||||
- Functions: test_smoke@L5
|
||||
- Classes: SmokeTests@L4
|
||||
- Anchors/search terms: SmokeTests, test_smoke
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
# Project Context
|
||||
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
project:
|
||||
name: tiny-lisp
|
||||
root: .
|
||||
task_file: tasks.md
|
||||
artifact_dir: .nightshift
|
||||
|
||||
safety:
|
||||
require_clean_worktree: false
|
||||
scoped_paths:
|
||||
- .
|
||||
allowed_commands:
|
||||
- python -m unittest discover -v
|
||||
forbidden_commands:
|
||||
- rm -rf
|
||||
- git push
|
||||
- curl | bash
|
||||
|
||||
agents:
|
||||
planner:
|
||||
backend: ollama
|
||||
model: qwen2.5-coder:14b
|
||||
temperature: 0.2
|
||||
system_prompt: agents/planner.md
|
||||
|
||||
implementer:
|
||||
backend: ollama
|
||||
model: qwen2.5-coder:14b
|
||||
temperature: 0.1
|
||||
system_prompt: agents/implementer.md
|
||||
|
||||
reviewer:
|
||||
backend: ollama
|
||||
model: qwen2.5-coder:14b
|
||||
temperature: 0.1
|
||||
system_prompt: agents/reviewer.md
|
||||
|
||||
pipeline:
|
||||
max_task_retries: 3
|
||||
continue_on_task_failure: false
|
||||
stages:
|
||||
- id: plan
|
||||
type: agent
|
||||
agent: planner
|
||||
output: plan.md
|
||||
|
||||
- id: context
|
||||
type: repo_context
|
||||
output: context-pack.md
|
||||
|
||||
- id: implement
|
||||
type: code_writer
|
||||
agent: implementer
|
||||
output: proposed.patch
|
||||
|
||||
- id: normalize
|
||||
type: patch_normalizer
|
||||
output: normalized.patch
|
||||
|
||||
- id: validate_patch
|
||||
type: patch_validator
|
||||
output: patch-validation.md
|
||||
max_files: 4
|
||||
max_lines: 400
|
||||
on_fail: implement
|
||||
|
||||
- id: apply_patch
|
||||
type: patch_apply
|
||||
mode: apply
|
||||
output: patch-apply-output.txt
|
||||
on_fail: implement
|
||||
|
||||
- id: test
|
||||
type: command
|
||||
commands:
|
||||
- python -m unittest discover -v
|
||||
output: test-output.txt
|
||||
shell: true
|
||||
timeout_seconds: 60
|
||||
on_fail: implement
|
||||
|
||||
- id: review
|
||||
type: agent_review
|
||||
agent: reviewer
|
||||
on_fail: implement
|
||||
output: review.md
|
||||
|
||||
- id: summarize
|
||||
type: summarize
|
||||
output: final-notes.md
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
You are the implementation agent for NightShift.
|
||||
|
||||
Output only a unified diff.
|
||||
Do not wrap the patch in markdown fences.
|
||||
Do not include explanations before or after the patch.
|
||||
Use diff --git headers.
|
||||
Include tests when needed.
|
||||
Keep the change as small as possible.
|
||||
Only edit files needed for the task.
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
You are the planning agent for NightShift.
|
||||
|
||||
Create a concise implementation plan for the current task.
|
||||
|
||||
If you need repository context before planning, output lookup requests exactly like this:
|
||||
|
||||
lookup_requests:
|
||||
- tool: read_file
|
||||
path: relative/path.py
|
||||
- tool: grep
|
||||
path: .
|
||||
pattern: search_regex
|
||||
|
||||
After context is provided, write a short plan with:
|
||||
- files to edit
|
||||
- tests to add or update
|
||||
- risks
|
||||
|
||||
Do not write code.
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
You are the review agent for NightShift.
|
||||
|
||||
Review the task, plan, patch artifacts, test output, and final state.
|
||||
|
||||
Output exactly:
|
||||
|
||||
status: pass | fail | retry | escalate
|
||||
reason: <short explanation>
|
||||
next_stage: <optional stage id>
|
||||
context_update: <compact useful note>
|
||||
|
||||
Use retry when the implementation is close but needs another patch.
|
||||
Use fail when the patch is unsafe, unrelated, or clearly broken.
|
||||
Use pass only when the acceptance criteria are satisfied.
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
# Run Metadata
|
||||
|
||||
Project: tiny-lisp
|
||||
Experiment label:
|
||||
Prompt variant:
|
||||
|
||||
## Agents
|
||||
|
||||
### planner
|
||||
|
||||
- Backend: ollama
|
||||
- Model: qwen2.5-coder:14b
|
||||
- Temperature: 0.2
|
||||
- Base URL:
|
||||
- Command:
|
||||
- System prompt: agents\planner.md
|
||||
|
||||
### implementer
|
||||
|
||||
- Backend: ollama
|
||||
- Model: qwen2.5-coder:14b
|
||||
- Temperature: 0.1
|
||||
- Base URL:
|
||||
- Command:
|
||||
- System prompt: agents\implementer.md
|
||||
|
||||
### reviewer
|
||||
|
||||
- Backend: ollama
|
||||
- Model: qwen2.5-coder:14b
|
||||
- Temperature: 0.1
|
||||
- Base URL:
|
||||
- Command:
|
||||
- System prompt: agents\reviewer.md
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
# Run Summary
|
||||
|
||||
- Task: TASK-001
|
||||
- Status: failed
|
||||
- Retry count: 3
|
||||
- Reason: Retry limit reached after stage 'apply_patch': Patch apply failed with code 128.
|
||||
- Experiment label:
|
||||
- Prompt variant:
|
||||
|
||||
## Modified Files
|
||||
|
||||
- Unavailable or none detected
|
||||
|
||||
## Artifacts
|
||||
|
||||
- Final notes: `tasks\TASK-001\final-notes.md`
|
||||
- Stage results: `tasks\TASK-001\stage-results.md`
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
diff --git a/lisp.py b/lisp.py
|
||||
index 0000000..f2a5c3d
|
||||
--- /dev/null
|
||||
+++ b/lisp.py
|
||||
@@ -0,0 +1,78 @@
|
||||
+import re
|
||||
+
|
||||
+class LispParser:
|
||||
+ def __init__(self, expression):
|
||||
+ self.expression = expression
|
||||
+ self.tokens = self.tokenize()
|
||||
+ self.index = 0
|
||||
+
|
||||
+ def tokenize(self):
|
||||
+ token_pattern = r'\s*([()])|(\d+)|([a-zA-Z_][a-zA-Z0-9_]*)'
|
||||
+ tokens = re.findall(token_pattern, self.expression)
|
||||
+ return [token for group in tokens for token in group if token]
|
||||
+
|
||||
+ def parse(self):
|
||||
+ result = []
|
||||
+ while self.index < len(self.tokens):
|
||||
+ token = self.tokens[self.index]
|
||||
+ if token == '(':
|
||||
+ self.index += 1
|
||||
+ result.append(self.parse())
|
||||
+ elif token == ')':
|
||||
+ return result
|
||||
+ else:
|
||||
+ result.append(self.parse_atom(token))
|
||||
+ self.index += 1
|
||||
+ if len(result) == 1:
|
||||
+ return result[0]
|
||||
+ return result
|
||||
+
|
||||
+ def parse_atom(self, token):
|
||||
+ try:
|
||||
+ return int(token)
|
||||
+ except ValueError:
|
||||
+ return token
|
||||
+
|
||||
+def parse_lisp(expression):
|
||||
+ parser = LispParser(expression)
|
||||
+ return parser.parse()
|
||||
+
|
||||
+if __name__ == "__main__":
|
||||
+ expression = "(1 2 (3 4) a b)"
|
||||
+ print(parse_lisp(expression))
|
||||
diff --git a/tests/test_lisp.py b/tests/test_lisp.py
|
||||
index 0000000..b5c7d8e
|
||||
--- /dev/null
|
||||
+++ b/tests/test_lisp.py
|
||||
@@ -0,0 +1,39 @@
|
||||
+import unittest
|
||||
+from lisp import parse_lisp
|
||||
+
|
||||
+class TestLispParser(unittest.TestCase):
|
||||
+ def test_parse_numbers(self):
|
||||
+ self.assertEqual(parse_lisp("123"), 123)
|
||||
+ self.assertEqual(parse_lisp("(1 2 3)"), [1, 2, 3])
|
||||
+
|
||||
+ def test_parse_symbols(self):
|
||||
+ self.assertEqual(parse_lisp("a"), "a")
|
||||
+ self.assertEqual(parse_lisp("(a b c)"), ["a", "b", "c"])
|
||||
+
|
||||
+ def test_parse_nested_lists(self):
|
||||
+ self.assertEqual(parse_lisp("(1 (2 3) 4)"), [1, [2, 3], 4])
|
||||
+ self.assertEqual(parse_lisp("((a b) (c d))"), [["a", "b"], ["c", "d"]])
|
||||
+
|
||||
+ def test_unbalanced_parentheses(self):
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp("(")
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp(")")
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp("(1 2 (3 4)")
|
||||
+
|
||||
+if __name__ == "__main__":
|
||||
+ unittest.main()
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
diff --git a/lisp.py b/lisp.py
|
||||
index 0000000..f2a5c3d
|
||||
--- /dev/null
|
||||
+++ b/lisp.py
|
||||
@@ -0,0 +1,78 @@
|
||||
+import re
|
||||
+
|
||||
+class LispParser:
|
||||
+ def __init__(self, expression):
|
||||
+ self.expression = expression
|
||||
+ self.tokens = self.tokenize()
|
||||
+ self.index = 0
|
||||
+
|
||||
+ def tokenize(self):
|
||||
+ token_pattern = r'\s*([()])|(\d+)|([a-zA-Z_][a-zA-Z0-9_]*)'
|
||||
+ tokens = re.findall(token_pattern, self.expression)
|
||||
+ return [token for group in tokens for token in group if token]
|
||||
+
|
||||
+ def parse(self):
|
||||
+ result = []
|
||||
+ while self.index < len(self.tokens):
|
||||
+ token = self.tokens[self.index]
|
||||
+ if token == '(':
|
||||
+ self.index += 1
|
||||
+ result.append(self.parse())
|
||||
+ elif token == ')':
|
||||
+ return result
|
||||
+ else:
|
||||
+ result.append(self.parse_atom(token))
|
||||
+ self.index += 1
|
||||
+ if len(result) == 1:
|
||||
+ return result[0]
|
||||
+ return result
|
||||
+
|
||||
+ def parse_atom(self, token):
|
||||
+ try:
|
||||
+ return int(token)
|
||||
+ except ValueError:
|
||||
+ return token
|
||||
+
|
||||
+def parse_lisp(expression):
|
||||
+ parser = LispParser(expression)
|
||||
+ return parser.parse()
|
||||
+
|
||||
+if __name__ == "__main__":
|
||||
+ expression = "(1 2 (3 4) a b)"
|
||||
+ print(parse_lisp(expression))
|
||||
diff --git a/tests/test_lisp.py b/tests/test_lisp.py
|
||||
index 0000000..b5c7d8e
|
||||
--- /dev/null
|
||||
+++ b/tests/test_lisp.py
|
||||
@@ -0,0 +1,39 @@
|
||||
+import unittest
|
||||
+from lisp import parse_lisp
|
||||
+
|
||||
+class TestLispParser(unittest.TestCase):
|
||||
+ def test_parse_numbers(self):
|
||||
+ self.assertEqual(parse_lisp("123"), 123)
|
||||
+ self.assertEqual(parse_lisp("(1 2 3)"), [1, 2, 3])
|
||||
+
|
||||
+ def test_parse_symbols(self):
|
||||
+ self.assertEqual(parse_lisp("a"), "a")
|
||||
+ self.assertEqual(parse_lisp("(a b c)"), ["a", "b", "c"])
|
||||
+
|
||||
+ def test_parse_nested_lists(self):
|
||||
+ self.assertEqual(parse_lisp("(1 (2 3) 4)"), [1, [2, 3], 4])
|
||||
+ self.assertEqual(parse_lisp("((a b) (c d))"), [["a", "b"], ["c", "d"]])
|
||||
+
|
||||
+ def test_unbalanced_parentheses(self):
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp("(")
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp(")")
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp("(1 2 (3 4)")
|
||||
+
|
||||
+if __name__ == "__main__":
|
||||
+ unittest.main()
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
diff --git a/lisp.py b/lisp.py
|
||||
index 0000000..f2a5c3d
|
||||
--- /dev/null
|
||||
+++ b/lisp.py
|
||||
@@ -0,0 +1,78 @@
|
||||
+import re
|
||||
+
|
||||
+class LispParser:
|
||||
+ def __init__(self, expression):
|
||||
+ self.expression = expression
|
||||
+ self.tokens = self.tokenize()
|
||||
+ self.index = 0
|
||||
+
|
||||
+ def tokenize(self):
|
||||
+ token_pattern = r'\s*([()])|(\d+)|([a-zA-Z_][a-zA-Z0-9_]*)'
|
||||
+ tokens = re.findall(token_pattern, self.expression)
|
||||
+ return [token for group in tokens for token in group if token]
|
||||
+
|
||||
+ def parse(self):
|
||||
+ result = []
|
||||
+ while self.index < len(self.tokens):
|
||||
+ token = self.tokens[self.index]
|
||||
+ if token == '(':
|
||||
+ self.index += 1
|
||||
+ result.append(self.parse())
|
||||
+ elif token == ')':
|
||||
+ return result
|
||||
+ else:
|
||||
+ result.append(self.parse_atom(token))
|
||||
+ self.index += 1
|
||||
+ if len(result) == 1:
|
||||
+ return result[0]
|
||||
+ return result
|
||||
+
|
||||
+ def parse_atom(self, token):
|
||||
+ try:
|
||||
+ return int(token)
|
||||
+ except ValueError:
|
||||
+ return token
|
||||
+
|
||||
+def parse_lisp(expression):
|
||||
+ parser = LispParser(expression)
|
||||
+ return parser.parse()
|
||||
+
|
||||
+if __name__ == "__main__":
|
||||
+ expression = "(1 2 (3 4) a b)"
|
||||
+ print(parse_lisp(expression))
|
||||
diff --git a/tests/test_lisp.py b/tests/test_lisp.py
|
||||
index 0000000..b5c7d8e
|
||||
--- /dev/null
|
||||
+++ b/tests/test_lisp.py
|
||||
@@ -0,0 +1,39 @@
|
||||
+import unittest
|
||||
+from lisp import parse_lisp
|
||||
+
|
||||
+class TestLispParser(unittest.TestCase):
|
||||
+ def test_parse_numbers(self):
|
||||
+ self.assertEqual(parse_lisp("123"), 123)
|
||||
+ self.assertEqual(parse_lisp("(1 2 3)"), [1, 2, 3])
|
||||
+
|
||||
+ def test_parse_symbols(self):
|
||||
+ self.assertEqual(parse_lisp("a"), "a")
|
||||
+ self.assertEqual(parse_lisp("(a b c)"), ["a", "b", "c"])
|
||||
+
|
||||
+ def test_parse_nested_lists(self):
|
||||
+ self.assertEqual(parse_lisp("(1 (2 3) 4)"), [1, [2, 3], 4])
|
||||
+ self.assertEqual(parse_lisp("((a b) (c d))"), [["a", "b"], ["c", "d"]])
|
||||
+
|
||||
+ def test_unbalanced_parentheses(self):
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp("(")
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp(")")
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp("(1 2 (3 4)")
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
# Context Out
|
||||
|
||||
Task: `TASK-001`
|
||||
Status: failed
|
||||
Reason: Retry limit reached after stage 'apply_patch': Patch apply failed with code 128.
|
||||
|
||||
## Retry Notes
|
||||
|
||||
- Context update from 'implement': Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/implementation-summary.md
|
||||
- Retry 1: stage 'validate_patch' returned fail (Patch validation failed: patch creates existing file `lisp.py`.); redirecting to 'implement'.
|
||||
- Context update from 'implement': Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-summary-1.md
|
||||
- Context update from 'apply_patch': C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:6: trailing whitespace.
|
||||
import re
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:7: trailing whitespace.
|
||||
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:8: trailing whitespace.
|
||||
class LispParser:
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:9: trailing whitespace.
|
||||
def __init__(self, expression):
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:10: trailing whitespace.
|
||||
self.expression = expression
|
||||
error: corrupt patch at line 48
|
||||
- Retry 2: stage 'apply_patch' returned fail (Patch apply failed with code 128.); redirecting to 'implement'.
|
||||
- Context update from 'implement': Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-summary-2.md
|
||||
- Context update from 'apply_patch': C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:6: trailing whitespace.
|
||||
import re
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:7: trailing whitespace.
|
||||
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:8: trailing whitespace.
|
||||
class LispParser:
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:9: trailing whitespace.
|
||||
def __init__(self, expression):
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:10: trailing whitespace.
|
||||
self.expression = expression
|
||||
error: corrupt patch at line 48
|
||||
- Retry 3: stage 'apply_patch' returned fail (Patch apply failed with code 128.); redirecting to 'implement'.
|
||||
- Context update from 'implement': Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-summary-3.md
|
||||
- Context update from 'apply_patch': C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:6: trailing whitespace.
|
||||
import re
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:7: trailing whitespace.
|
||||
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:8: trailing whitespace.
|
||||
class LispParser:
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:9: trailing whitespace.
|
||||
def __init__(self, expression):
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:10: trailing whitespace.
|
||||
self.expression = expression
|
||||
error: corrupt patch at line 48
|
||||
|
||||
## Durable Notes
|
||||
|
||||
- Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/implementation-summary.md
|
||||
- Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-summary-1.md
|
||||
- C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:6: trailing whitespace.
|
||||
import re
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:7: trailing whitespace.
|
||||
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:8: trailing whitespace.
|
||||
class LispParser:
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:9: trailing whitespace.
|
||||
def __init__(self, expression):
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:10: trailing whitespace.
|
||||
self.expression = expression
|
||||
error: corrupt patch at line 48
|
||||
- Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-summary-2.md
|
||||
- C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:6: trailing whitespace.
|
||||
import re
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:7: trailing whitespace.
|
||||
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:8: trailing whitespace.
|
||||
class LispParser:
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:9: trailing whitespace.
|
||||
def __init__(self, expression):
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:10: trailing whitespace.
|
||||
self.expression = expression
|
||||
error: corrupt patch at line 48
|
||||
- Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-summary-3.md
|
||||
- C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:6: trailing whitespace.
|
||||
import re
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:7: trailing whitespace.
|
||||
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:8: trailing whitespace.
|
||||
class LispParser:
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:9: trailing whitespace.
|
||||
def __init__(self, expression):
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:10: trailing whitespace.
|
||||
self.expression = expression
|
||||
error: corrupt patch at line 48
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
# Context Pack
|
||||
|
||||
Task: `TASK-001`
|
||||
Title: Parse Lisp expressions
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- Parses numbers
|
||||
- Parses symbols
|
||||
- Parses nested lists
|
||||
- Raises useful errors for unbalanced parentheses
|
||||
- Includes unit tests
|
||||
|
||||
## Constraints
|
||||
|
||||
- Scoped paths: .
|
||||
- Repository lookups are read-only.
|
||||
- Excerpts are line-numbered where files are read directly.
|
||||
|
||||
## Relevant Files
|
||||
|
||||
```text
|
||||
lisp.py
|
||||
tests/__init__.py
|
||||
tests/test_lisp.py
|
||||
```
|
||||
|
||||
## Search Results
|
||||
|
||||
### Search: parse
|
||||
|
||||
```text
|
||||
tasks.md:21: Evaluate parsed arithmetic expressions.
|
||||
```
|
||||
|
||||
### Search: lisp
|
||||
|
||||
```text
|
||||
nightshift.yaml:2: name: tiny-lisp
|
||||
```
|
||||
|
||||
### Search: expressions
|
||||
|
||||
```text
|
||||
tasks.md:3: - [ ] TASK-001: Parse Lisp expressions
|
||||
tasks.md:21: Evaluate parsed arithmetic expressions.
|
||||
tasks.md:47: Implement simple truthiness and `if` expressions.
|
||||
```
|
||||
|
||||
### Search: parses
|
||||
|
||||
```text
|
||||
No matches found.
|
||||
```
|
||||
|
||||
### Search: numbers
|
||||
|
||||
```text
|
||||
tasks.md:9: - Parses numbers
|
||||
```
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
# Task Context
|
||||
|
||||
Task: `TASK-001`
|
||||
Title: Parse Lisp expressions
|
||||
|
||||
## Description
|
||||
|
||||
Implement tokenization and parsing for a tiny Lisp subset.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- Parses numbers
|
||||
- Parses symbols
|
||||
- Parses nested lists
|
||||
- Raises useful errors for unbalanced parentheses
|
||||
- Includes unit tests
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
Git diff unavailable.
|
||||
|
||||
Reason: project root is not a git work tree.
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
# Repo Tool Calls
|
||||
|
||||
## 1. read_file
|
||||
|
||||
Arguments:
|
||||
- path: `lisp_parser.py`
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
File not found: lisp_parser.py
|
||||
```
|
||||
|
||||
## 2. read_file
|
||||
|
||||
Arguments:
|
||||
- path: `tests/test_lisp_parser.py`
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
File not found: tests/test_lisp_parser.py
|
||||
```
|
||||
|
||||
## 3. list_files
|
||||
|
||||
Arguments:
|
||||
- path: `.`
|
||||
- pattern: `*.py`
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
lisp.py
|
||||
tests/__init__.py
|
||||
tests/test_lisp.py
|
||||
```
|
||||
|
||||
## 4. grep
|
||||
|
||||
Arguments:
|
||||
- path: `.`
|
||||
- pattern: `def parse\(`
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
No matches found.
|
||||
```
|
||||
|
||||
## 5. grep
|
||||
|
||||
Arguments:
|
||||
- path: `.`
|
||||
- pattern: `class LispParser`
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
No matches found.
|
||||
```
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
# Final Task Notes
|
||||
|
||||
Task: `TASK-001`
|
||||
Title: Parse Lisp expressions
|
||||
Status: failed
|
||||
Retry count: 3
|
||||
Reason: Retry limit reached after stage 'apply_patch': Patch apply failed with code 128.
|
||||
|
||||
## Experiment
|
||||
|
||||
- Label:
|
||||
- Prompt variant:
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- Parses numbers
|
||||
- Parses symbols
|
||||
- Parses nested lists
|
||||
- Raises useful errors for unbalanced parentheses
|
||||
- Includes unit tests
|
||||
|
||||
## Stage Results
|
||||
|
||||
- `plan`: pass (Agent completed after repo lookup.)
|
||||
- `context`: pass (Context pack written.)
|
||||
- `implement`: pass (Proposed patch written.)
|
||||
- `normalize`: pass (Normalized patch written.)
|
||||
- `validate_patch`: fail (Patch validation failed: patch creates existing file `lisp.py`.)
|
||||
- `implement`: pass (Proposed patch written.)
|
||||
- `normalize`: pass (Normalized patch written.)
|
||||
- `validate_patch`: pass (Patch validation passed.)
|
||||
- `apply_patch`: fail (Patch apply failed with code 128.)
|
||||
- `implement`: pass (Proposed patch written.)
|
||||
- `normalize`: pass (Normalized patch written.)
|
||||
- `validate_patch`: pass (Patch validation passed.)
|
||||
- `apply_patch`: fail (Patch apply failed with code 128.)
|
||||
- `implement`: pass (Proposed patch written.)
|
||||
- `normalize`: pass (Normalized patch written.)
|
||||
- `validate_patch`: pass (Patch validation passed.)
|
||||
- `apply_patch`: fail (Patch apply failed with code 128.)
|
||||
|
||||
## Modified Files
|
||||
|
||||
- Unavailable or none detected
|
||||
|
||||
## Artifacts
|
||||
|
||||
- Stage results: `stage-results.md`
|
||||
- Context out: `context-out.md`
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
# Git Status after-patch-apply
|
||||
|
||||
Available: false
|
||||
Exit code: 128
|
||||
|
||||
## stdout
|
||||
|
||||
```text
|
||||
|
||||
```
|
||||
|
||||
## stderr
|
||||
|
||||
```text
|
||||
fatal: not a git repository (or any of the parent directories): .git
|
||||
```
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
# Git Status after
|
||||
|
||||
Available: false
|
||||
Exit code: 128
|
||||
|
||||
## stdout
|
||||
|
||||
```text
|
||||
|
||||
```
|
||||
|
||||
## stderr
|
||||
|
||||
```text
|
||||
fatal: not a git repository (or any of the parent directories): .git
|
||||
```
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
# Git Status before-patch-apply
|
||||
|
||||
Available: false
|
||||
Exit code: 128
|
||||
|
||||
## stdout
|
||||
|
||||
```text
|
||||
|
||||
```
|
||||
|
||||
## stderr
|
||||
|
||||
```text
|
||||
fatal: not a git repository (or any of the parent directories): .git
|
||||
```
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
# Git Status before
|
||||
|
||||
Available: false
|
||||
Exit code: 128
|
||||
|
||||
## stdout
|
||||
|
||||
```text
|
||||
|
||||
```
|
||||
|
||||
## stderr
|
||||
|
||||
```text
|
||||
fatal: not a git repository (or any of the parent directories): .git
|
||||
```
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
# Implementation Summary
|
||||
|
||||
Stage: `implement`
|
||||
Status: pass
|
||||
Repair attempt: 0
|
||||
Patch: `.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/proposed.patch`
|
||||
|
||||
## Retry Feedback
|
||||
|
||||
- None
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
diff --git a/lisp.py b/lisp.py
|
||||
index 0000000..f2a5c3d
|
||||
--- /dev/null
|
||||
+++ b/lisp.py
|
||||
@@ -0,0 +1,78 @@
|
||||
+import re
|
||||
+
|
||||
+class LispParser:
|
||||
+ def __init__(self, expression):
|
||||
+ self.expression = expression
|
||||
+ self.tokens = self.tokenize()
|
||||
+ self.index = 0
|
||||
+
|
||||
+ def tokenize(self):
|
||||
+ token_pattern = r'\s*([()])|(\d+)|([a-zA-Z_][a-zA-Z0-9_]*)'
|
||||
+ tokens = re.findall(token_pattern, self.expression)
|
||||
+ return [token for group in tokens for token in group if token]
|
||||
+
|
||||
+ def parse(self):
|
||||
+ result = []
|
||||
+ while self.index < len(self.tokens):
|
||||
+ token = self.tokens[self.index]
|
||||
+ if token == '(':
|
||||
+ self.index += 1
|
||||
+ result.append(self.parse())
|
||||
+ elif token == ')':
|
||||
+ return result
|
||||
+ else:
|
||||
+ result.append(self.parse_atom(token))
|
||||
+ self.index += 1
|
||||
+ if len(result) == 1:
|
||||
+ return result[0]
|
||||
+ return result
|
||||
+
|
||||
+ def parse_atom(self, token):
|
||||
+ try:
|
||||
+ return int(token)
|
||||
+ except ValueError:
|
||||
+ return token
|
||||
+
|
||||
+def parse_lisp(expression):
|
||||
+ parser = LispParser(expression)
|
||||
+ return parser.parse()
|
||||
+
|
||||
+if __name__ == "__main__":
|
||||
+ expression = "(1 2 (3 4) a b)"
|
||||
+ print(parse_lisp(expression))
|
||||
diff --git a/tests/test_lisp.py b/tests/test_lisp.py
|
||||
index 0000000..b5c7d8e
|
||||
--- /dev/null
|
||||
+++ b/tests/test_lisp.py
|
||||
@@ -0,0 +1,39 @@
|
||||
+import unittest
|
||||
+from lisp import parse_lisp
|
||||
+
|
||||
+class TestLispParser(unittest.TestCase):
|
||||
+ def test_parse_numbers(self):
|
||||
+ self.assertEqual(parse_lisp("123"), 123)
|
||||
+ self.assertEqual(parse_lisp("(1 2 3)"), [1, 2, 3])
|
||||
+
|
||||
+ def test_parse_symbols(self):
|
||||
+ self.assertEqual(parse_lisp("a"), "a")
|
||||
+ self.assertEqual(parse_lisp("(a b c)"), ["a", "b", "c"])
|
||||
+
|
||||
+ def test_parse_nested_lists(self):
|
||||
+ self.assertEqual(parse_lisp("(1 (2 3) 4)"), [1, [2, 3], 4])
|
||||
+ self.assertEqual(parse_lisp("((a b) (c d))"), [["a", "b"], ["c", "d"]])
|
||||
+
|
||||
+ def test_unbalanced_parentheses(self):
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp("(")
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp(")")
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp("(1 2 (3 4)")
|
||||
+
|
||||
+if __name__ == "__main__":
|
||||
+ unittest.main()
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
diff --git a/lisp.py b/lisp.py
|
||||
index 0000000..f2a5c3d
|
||||
--- /dev/null
|
||||
+++ b/lisp.py
|
||||
@@ -0,0 +1,78 @@
|
||||
+import re
|
||||
+
|
||||
+class LispParser:
|
||||
+ def __init__(self, expression):
|
||||
+ self.expression = expression
|
||||
+ self.tokens = self.tokenize()
|
||||
+ self.index = 0
|
||||
+
|
||||
+ def tokenize(self):
|
||||
+ token_pattern = r'\s*([()])|(\d+)|([a-zA-Z_][a-zA-Z0-9_]*)'
|
||||
+ tokens = re.findall(token_pattern, self.expression)
|
||||
+ return [token for group in tokens for token in group if token]
|
||||
+
|
||||
+ def parse(self):
|
||||
+ result = []
|
||||
+ while self.index < len(self.tokens):
|
||||
+ token = self.tokens[self.index]
|
||||
+ if token == '(':
|
||||
+ self.index += 1
|
||||
+ result.append(self.parse())
|
||||
+ elif token == ')':
|
||||
+ return result
|
||||
+ else:
|
||||
+ result.append(self.parse_atom(token))
|
||||
+ self.index += 1
|
||||
+ if len(result) == 1:
|
||||
+ return result[0]
|
||||
+ return result
|
||||
+
|
||||
+ def parse_atom(self, token):
|
||||
+ try:
|
||||
+ return int(token)
|
||||
+ except ValueError:
|
||||
+ return token
|
||||
+
|
||||
+def parse_lisp(expression):
|
||||
+ parser = LispParser(expression)
|
||||
+ return parser.parse()
|
||||
+
|
||||
+if __name__ == "__main__":
|
||||
+ expression = "(1 2 (3 4) a b)"
|
||||
+ print(parse_lisp(expression))
|
||||
diff --git a/tests/test_lisp.py b/tests/test_lisp.py
|
||||
index 0000000..b5c7d8e
|
||||
--- /dev/null
|
||||
+++ b/tests/test_lisp.py
|
||||
@@ -0,0 +1,39 @@
|
||||
+import unittest
|
||||
+from lisp import parse_lisp
|
||||
+
|
||||
+class TestLispParser(unittest.TestCase):
|
||||
+ def test_parse_numbers(self):
|
||||
+ self.assertEqual(parse_lisp("123"), 123)
|
||||
+ self.assertEqual(parse_lisp("(1 2 3)"), [1, 2, 3])
|
||||
+
|
||||
+ def test_parse_symbols(self):
|
||||
+ self.assertEqual(parse_lisp("a"), "a")
|
||||
+ self.assertEqual(parse_lisp("(a b c)"), ["a", "b", "c"])
|
||||
+
|
||||
+ def test_parse_nested_lists(self):
|
||||
+ self.assertEqual(parse_lisp("(1 (2 3) 4)"), [1, [2, 3], 4])
|
||||
+ self.assertEqual(parse_lisp("((a b) (c d))"), [["a", "b"], ["c", "d"]])
|
||||
+
|
||||
+ def test_unbalanced_parentheses(self):
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp("(")
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp(")")
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp("(1 2 (3 4)")
|
||||
+
|
||||
+if __name__ == "__main__":
|
||||
+ unittest.main()
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
diff --git a/lisp.py b/lisp.py
|
||||
index 0000000..f2a5c3d
|
||||
--- /dev/null
|
||||
+++ b/lisp.py
|
||||
@@ -0,0 +1,78 @@
|
||||
+import re
|
||||
+
|
||||
+class LispParser:
|
||||
+ def __init__(self, expression):
|
||||
+ self.expression = expression
|
||||
+ self.tokens = self.tokenize()
|
||||
+ self.index = 0
|
||||
+
|
||||
+ def tokenize(self):
|
||||
+ token_pattern = r'\s*([()])|(\d+)|([a-zA-Z_][a-zA-Z0-9_]*)'
|
||||
+ tokens = re.findall(token_pattern, self.expression)
|
||||
+ return [token for group in tokens for token in group if token]
|
||||
+
|
||||
+ def parse(self):
|
||||
+ result = []
|
||||
+ while self.index < len(self.tokens):
|
||||
+ token = self.tokens[self.index]
|
||||
+ if token == '(':
|
||||
+ self.index += 1
|
||||
+ result.append(self.parse())
|
||||
+ elif token == ')':
|
||||
+ return result
|
||||
+ else:
|
||||
+ result.append(self.parse_atom(token))
|
||||
+ self.index += 1
|
||||
+ if len(result) == 1:
|
||||
+ return result[0]
|
||||
+ return result
|
||||
+
|
||||
+ def parse_atom(self, token):
|
||||
+ try:
|
||||
+ return int(token)
|
||||
+ except ValueError:
|
||||
+ return token
|
||||
+
|
||||
+def parse_lisp(expression):
|
||||
+ parser = LispParser(expression)
|
||||
+ return parser.parse()
|
||||
+
|
||||
+if __name__ == "__main__":
|
||||
+ expression = "(1 2 (3 4) a b)"
|
||||
+ print(parse_lisp(expression))
|
||||
diff --git a/tests/test_lisp.py b/tests/test_lisp.py
|
||||
index 0000000..b5c7d8e
|
||||
--- /dev/null
|
||||
+++ b/tests/test_lisp.py
|
||||
@@ -0,0 +1,39 @@
|
||||
+import unittest
|
||||
+from lisp import parse_lisp
|
||||
+
|
||||
+class TestLispParser(unittest.TestCase):
|
||||
+ def test_parse_numbers(self):
|
||||
+ self.assertEqual(parse_lisp("123"), 123)
|
||||
+ self.assertEqual(parse_lisp("(1 2 3)"), [1, 2, 3])
|
||||
+
|
||||
+ def test_parse_symbols(self):
|
||||
+ self.assertEqual(parse_lisp("a"), "a")
|
||||
+ self.assertEqual(parse_lisp("(a b c)"), ["a", "b", "c"])
|
||||
+
|
||||
+ def test_parse_nested_lists(self):
|
||||
+ self.assertEqual(parse_lisp("(1 (2 3) 4)"), [1, [2, 3], 4])
|
||||
+ self.assertEqual(parse_lisp("((a b) (c d))"), [["a", "b"], ["c", "d"]])
|
||||
+
|
||||
+ def test_unbalanced_parentheses(self):
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp("(")
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp(")")
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp("(1 2 (3 4)")
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
diff --git a/lisp.py b/lisp.py
|
||||
new file mode 100644
|
||||
index 0000000..f2a5c3d
|
||||
--- /dev/null
|
||||
+++ b/lisp.py
|
||||
@@ -0,0 +1,78 @@
|
||||
+import re
|
||||
+
|
||||
+class LispParser:
|
||||
+ def __init__(self, expression):
|
||||
+ self.expression = expression
|
||||
+ self.tokens = self.tokenize()
|
||||
+ self.index = 0
|
||||
+
|
||||
+ def tokenize(self):
|
||||
+ token_pattern = r'\s*([()])|(\d+)|([a-zA-Z_][a-zA-Z0-9_]*)'
|
||||
+ tokens = re.findall(token_pattern, self.expression)
|
||||
+ return [token for group in tokens for token in group if token]
|
||||
+
|
||||
+ def parse(self):
|
||||
+ result = []
|
||||
+ while self.index < len(self.tokens):
|
||||
+ token = self.tokens[self.index]
|
||||
+ if token == '(':
|
||||
+ self.index += 1
|
||||
+ result.append(self.parse())
|
||||
+ elif token == ')':
|
||||
+ return result
|
||||
+ else:
|
||||
+ result.append(self.parse_atom(token))
|
||||
+ self.index += 1
|
||||
+ if len(result) == 1:
|
||||
+ return result[0]
|
||||
+ return result
|
||||
+
|
||||
+ def parse_atom(self, token):
|
||||
+ try:
|
||||
+ return int(token)
|
||||
+ except ValueError:
|
||||
+ return token
|
||||
+
|
||||
+def parse_lisp(expression):
|
||||
+ parser = LispParser(expression)
|
||||
+ return parser.parse()
|
||||
+
|
||||
+if __name__ == "__main__":
|
||||
+ expression = "(1 2 (3 4) a b)"
|
||||
+ print(parse_lisp(expression))
|
||||
diff --git a/tests/test_lisp.py b/tests/test_lisp.py
|
||||
new file mode 100644
|
||||
index 0000000..b5c7d8e
|
||||
--- /dev/null
|
||||
+++ b/tests/test_lisp.py
|
||||
@@ -0,0 +1,39 @@
|
||||
+import unittest
|
||||
+from lisp import parse_lisp
|
||||
+
|
||||
+class TestLispParser(unittest.TestCase):
|
||||
+ def test_parse_numbers(self):
|
||||
+ self.assertEqual(parse_lisp("123"), 123)
|
||||
+ self.assertEqual(parse_lisp("(1 2 3)"), [1, 2, 3])
|
||||
+
|
||||
+ def test_parse_symbols(self):
|
||||
+ self.assertEqual(parse_lisp("a"), "a")
|
||||
+ self.assertEqual(parse_lisp("(a b c)"), ["a", "b", "c"])
|
||||
+
|
||||
+ def test_parse_nested_lists(self):
|
||||
+ self.assertEqual(parse_lisp("(1 (2 3) 4)"), [1, [2, 3], 4])
|
||||
+ self.assertEqual(parse_lisp("((a b) (c d))"), [["a", "b"], ["c", "d"]])
|
||||
+
|
||||
+ def test_unbalanced_parentheses(self):
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp("(")
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp(")")
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp("(1 2 (3 4)")
|
||||
+
|
||||
+if __name__ == "__main__":
|
||||
+ unittest.main()
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
# Patch Apply
|
||||
|
||||
Status: fail
|
||||
Mode: apply
|
||||
Patch: `.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch`
|
||||
Command: `git apply --ignore-whitespace C:\Users\metis\Documents\tiny-lisp-nightshift\.nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\applied-1.patch`
|
||||
Exit code: 128
|
||||
|
||||
## stdout
|
||||
|
||||
```text
|
||||
|
||||
```
|
||||
|
||||
## stderr
|
||||
|
||||
```text
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:6: trailing whitespace.
|
||||
import re
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:7: trailing whitespace.
|
||||
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:8: trailing whitespace.
|
||||
class LispParser:
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:9: trailing whitespace.
|
||||
def __init__(self, expression):
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:10: trailing whitespace.
|
||||
self.expression = expression
|
||||
error: corrupt patch at line 48
|
||||
```
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
# Patch Apply
|
||||
|
||||
Status: fail
|
||||
Mode: apply
|
||||
Patch: `.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch`
|
||||
Command: `git apply --ignore-whitespace C:\Users\metis\Documents\tiny-lisp-nightshift\.nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\applied-2.patch`
|
||||
Exit code: 128
|
||||
|
||||
## stdout
|
||||
|
||||
```text
|
||||
|
||||
```
|
||||
|
||||
## stderr
|
||||
|
||||
```text
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:6: trailing whitespace.
|
||||
import re
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:7: trailing whitespace.
|
||||
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:8: trailing whitespace.
|
||||
class LispParser:
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:9: trailing whitespace.
|
||||
def __init__(self, expression):
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:10: trailing whitespace.
|
||||
self.expression = expression
|
||||
error: corrupt patch at line 48
|
||||
```
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
# Patch Apply
|
||||
|
||||
Status: fail
|
||||
Mode: apply
|
||||
Patch: `.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch`
|
||||
Command: `git apply --ignore-whitespace C:\Users\metis\Documents\tiny-lisp-nightshift\.nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\applied-3.patch`
|
||||
Exit code: 128
|
||||
|
||||
## stdout
|
||||
|
||||
```text
|
||||
|
||||
```
|
||||
|
||||
## stderr
|
||||
|
||||
```text
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:6: trailing whitespace.
|
||||
import re
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:7: trailing whitespace.
|
||||
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:8: trailing whitespace.
|
||||
class LispParser:
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:9: trailing whitespace.
|
||||
def __init__(self, expression):
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:10: trailing whitespace.
|
||||
self.expression = expression
|
||||
error: corrupt patch at line 48
|
||||
```
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
# Patch Validation
|
||||
|
||||
Status: pass
|
||||
Changed files: 2
|
||||
Changed lines: 68
|
||||
|
||||
## Files
|
||||
|
||||
- `lisp.py`
|
||||
- `tests/test_lisp.py`
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
# Patch Validation
|
||||
|
||||
Status: pass
|
||||
Changed files: 2
|
||||
Changed lines: 68
|
||||
|
||||
## Files
|
||||
|
||||
- `lisp.py`
|
||||
- `tests/test_lisp.py`
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
# Patch Validation
|
||||
|
||||
Status: pass
|
||||
Changed files: 2
|
||||
Changed lines: 65
|
||||
|
||||
## Files
|
||||
|
||||
- `lisp.py`
|
||||
- `tests/test_lisp.py`
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
# Patch Validation
|
||||
|
||||
Status: fail
|
||||
Reason: Patch validation failed: patch creates existing file `lisp.py`.
|
||||
|
|
@ -1,193 +0,0 @@
|
|||
# Agent Output: plan
|
||||
|
||||
Agent: `planner`
|
||||
Command: `POST http://localhost:11434/api/generate`
|
||||
Exit code: 0
|
||||
Duration seconds: 0.942
|
||||
Timed out: false
|
||||
|
||||
## stdout
|
||||
|
||||
```text
|
||||
lookup_requests:
|
||||
- tool: read_file
|
||||
path: lisp.py
|
||||
- tool: read_file
|
||||
path: tests/test_lisp.py
|
||||
|
||||
After context is provided, write a short plan with:
|
||||
- files to edit
|
||||
- tests to add or update
|
||||
- risks
|
||||
```
|
||||
|
||||
## stderr
|
||||
|
||||
```text
|
||||
|
||||
```
|
||||
|
||||
## Prompt
|
||||
|
||||
```markdown
|
||||
# NightShift Agent Input
|
||||
|
||||
## System Prompt
|
||||
|
||||
You are the planning agent for NightShift.
|
||||
|
||||
Create a concise implementation plan for the current task.
|
||||
|
||||
If you need repository context before planning, output lookup requests exactly like this:
|
||||
|
||||
lookup_requests:
|
||||
- tool: read_file
|
||||
path: relative/path.py
|
||||
- tool: grep
|
||||
path: .
|
||||
pattern: search_regex
|
||||
|
||||
After context is provided, write a short plan with:
|
||||
- files to edit
|
||||
- tests to add or update
|
||||
- risks
|
||||
|
||||
Do not write code.
|
||||
|
||||
## Stage
|
||||
|
||||
- id: plan
|
||||
- type: agent
|
||||
|
||||
## Task
|
||||
|
||||
- [ ] TASK-001: Parse Lisp expressions
|
||||
|
||||
Description:
|
||||
Implement tokenization and parsing for a tiny Lisp subset.
|
||||
|
||||
Acceptance Criteria:
|
||||
- Parses numbers
|
||||
- Parses symbols
|
||||
- Parses nested lists
|
||||
- Raises useful errors for unbalanced parentheses
|
||||
- Includes unit tests
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- Parses numbers
|
||||
- Parses symbols
|
||||
- Parses nested lists
|
||||
- Raises useful errors for unbalanced parentheses
|
||||
- Includes unit tests
|
||||
|
||||
## Project Context
|
||||
|
||||
# Project Context
|
||||
|
||||
## Task Context
|
||||
|
||||
# Task Context
|
||||
|
||||
Task: `TASK-001`
|
||||
Title: Parse Lisp expressions
|
||||
|
||||
## Description
|
||||
|
||||
Implement tokenization and parsing for a tiny Lisp subset.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- Parses numbers
|
||||
- Parses symbols
|
||||
- Parses nested lists
|
||||
- Raises useful errors for unbalanced parentheses
|
||||
- Includes unit tests
|
||||
|
||||
## Previous Stage Output
|
||||
|
||||
## repo_lookup_results
|
||||
|
||||
# Repo Tool Calls
|
||||
|
||||
## 1. read_file
|
||||
|
||||
Arguments:
|
||||
- path: `lisp_parser.py`
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
File not found: lisp_parser.py
|
||||
```
|
||||
|
||||
## 2. read_file
|
||||
|
||||
Arguments:
|
||||
- path: `tests/test_lisp_parser.py`
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
File not found: tests/test_lisp_parser.py
|
||||
```
|
||||
|
||||
## 3. list_files
|
||||
|
||||
Arguments:
|
||||
- path: `.`
|
||||
- pattern: `*.py`
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
lisp.py
|
||||
tests/__init__.py
|
||||
tests/test_lisp.py
|
||||
```
|
||||
|
||||
## 4. grep
|
||||
|
||||
Arguments:
|
||||
- path: `.`
|
||||
- pattern: `def parse\(`
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
No matches found.
|
||||
```
|
||||
|
||||
## 5. grep
|
||||
|
||||
Arguments:
|
||||
- path: `.`
|
||||
- pattern: `class LispParser`
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
No matches found.
|
||||
```
|
||||
|
||||
## Retry Notes
|
||||
|
||||
- Repository lookup results have been provided. Write the final plan now; do not request more lookups.
|
||||
|
||||
## Output Contract
|
||||
|
||||
Write the requested stage output in concise markdown.
|
||||
|
||||
If you need repository context before finalizing the plan, include:
|
||||
lookup_requests:
|
||||
- tool: list_files | read_file | grep
|
||||
path: <relative path>
|
||||
pattern: <glob for list_files or regex for grep>
|
||||
|
||||
Use at most 5 lookup requests.
|
||||
Do not repeat the same lookup request.
|
||||
Prefer read_file for likely-relevant files over many grep variations.
|
||||
Do not search .nightshift, .git, virtualenvs, caches, or artifact directories.
|
||||
|
||||
NightShift will run these read-only lookup tools, save files-inspected.md, and re-run this planner stage with the retrieved context.
|
||||
```
|
||||
|
|
@ -1,825 +0,0 @@
|
|||
# Agent Output: implement
|
||||
|
||||
Agent: `implementer`
|
||||
Command: `POST http://localhost:11434/api/generate`
|
||||
Exit code: 0
|
||||
Duration seconds: 10.410
|
||||
Timed out: false
|
||||
|
||||
## stdout
|
||||
|
||||
```text
|
||||
diff --git a/lisp.py b/lisp.py
|
||||
index 0000000..f2a5c3d
|
||||
--- /dev/null
|
||||
+++ b/lisp.py
|
||||
@@ -0,0 +1,78 @@
|
||||
+import re
|
||||
+
|
||||
+class LispParser:
|
||||
+ def __init__(self, expression):
|
||||
+ self.expression = expression
|
||||
+ self.tokens = self.tokenize()
|
||||
+ self.index = 0
|
||||
+
|
||||
+ def tokenize(self):
|
||||
+ token_pattern = r'\s*([()])|(\d+)|([a-zA-Z_][a-zA-Z0-9_]*)'
|
||||
+ tokens = re.findall(token_pattern, self.expression)
|
||||
+ return [token for group in tokens for token in group if token]
|
||||
+
|
||||
+ def parse(self):
|
||||
+ result = []
|
||||
+ while self.index < len(self.tokens):
|
||||
+ token = self.tokens[self.index]
|
||||
+ if token == '(':
|
||||
+ self.index += 1
|
||||
+ result.append(self.parse())
|
||||
+ elif token == ')':
|
||||
+ return result
|
||||
+ else:
|
||||
+ result.append(self.parse_atom(token))
|
||||
+ self.index += 1
|
||||
+ if len(result) == 1:
|
||||
+ return result[0]
|
||||
+ return result
|
||||
+
|
||||
+ def parse_atom(self, token):
|
||||
+ try:
|
||||
+ return int(token)
|
||||
+ except ValueError:
|
||||
+ return token
|
||||
+
|
||||
+def parse_lisp(expression):
|
||||
+ parser = LispParser(expression)
|
||||
+ return parser.parse()
|
||||
+
|
||||
+if __name__ == "__main__":
|
||||
+ expression = "(1 2 (3 4) a b)"
|
||||
+ print(parse_lisp(expression))
|
||||
diff --git a/tests/test_lisp.py b/tests/test_lisp.py
|
||||
index 0000000..b5c7d8e
|
||||
--- /dev/null
|
||||
+++ b/tests/test_lisp.py
|
||||
@@ -0,0 +1,39 @@
|
||||
+import unittest
|
||||
+from lisp import parse_lisp
|
||||
+
|
||||
+class TestLispParser(unittest.TestCase):
|
||||
+ def test_parse_numbers(self):
|
||||
+ self.assertEqual(parse_lisp("123"), 123)
|
||||
+ self.assertEqual(parse_lisp("(1 2 3)"), [1, 2, 3])
|
||||
+
|
||||
+ def test_parse_symbols(self):
|
||||
+ self.assertEqual(parse_lisp("a"), "a")
|
||||
+ self.assertEqual(parse_lisp("(a b c)"), ["a", "b", "c"])
|
||||
+
|
||||
+ def test_parse_nested_lists(self):
|
||||
+ self.assertEqual(parse_lisp("(1 (2 3) 4)"), [1, [2, 3], 4])
|
||||
+ self.assertEqual(parse_lisp("((a b) (c d))"), [["a", "b"], ["c", "d"]])
|
||||
+
|
||||
+ def test_unbalanced_parentheses(self):
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp("(")
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp(")")
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp("(1 2 (3 4)")
|
||||
```
|
||||
|
||||
## stderr
|
||||
|
||||
```text
|
||||
|
||||
```
|
||||
|
||||
## Prompt
|
||||
|
||||
```markdown
|
||||
# NightShift Agent Input
|
||||
|
||||
## System Prompt
|
||||
|
||||
You are the implementation agent for NightShift.
|
||||
|
||||
Output only a unified diff.
|
||||
Do not wrap the patch in markdown fences.
|
||||
Do not include explanations before or after the patch.
|
||||
Use diff --git headers.
|
||||
Include tests when needed.
|
||||
Keep the change as small as possible.
|
||||
Only edit files needed for the task.
|
||||
|
||||
## Stage
|
||||
|
||||
- id: implement
|
||||
- type: code_writer
|
||||
|
||||
## Task
|
||||
|
||||
- [ ] TASK-001: Parse Lisp expressions
|
||||
|
||||
Description:
|
||||
Implement tokenization and parsing for a tiny Lisp subset.
|
||||
|
||||
Acceptance Criteria:
|
||||
- Parses numbers
|
||||
- Parses symbols
|
||||
- Parses nested lists
|
||||
- Raises useful errors for unbalanced parentheses
|
||||
- Includes unit tests
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- Parses numbers
|
||||
- Parses symbols
|
||||
- Parses nested lists
|
||||
- Raises useful errors for unbalanced parentheses
|
||||
- Includes unit tests
|
||||
|
||||
## Project Context
|
||||
|
||||
# Project Context
|
||||
|
||||
## Task Context
|
||||
|
||||
# Task Context
|
||||
|
||||
Task: `TASK-001`
|
||||
Title: Parse Lisp expressions
|
||||
|
||||
## Description
|
||||
|
||||
Implement tokenization and parsing for a tiny Lisp subset.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- Parses numbers
|
||||
- Parses symbols
|
||||
- Parses nested lists
|
||||
- Raises useful errors for unbalanced parentheses
|
||||
- Includes unit tests
|
||||
|
||||
## Previous Stage Output
|
||||
|
||||
## plan
|
||||
|
||||
# Agent Output: plan
|
||||
|
||||
Agent: `planner`
|
||||
Command: `POST http://localhost:11434/api/generate`
|
||||
Exit code: 0
|
||||
Duration seconds: 0.942
|
||||
Timed out: false
|
||||
|
||||
## stdout
|
||||
|
||||
```text
|
||||
lookup_requests:
|
||||
- tool: read_file
|
||||
path: lisp.py
|
||||
- tool: read_file
|
||||
path: tests/test_lisp.py
|
||||
|
||||
After context is provided, write a short plan with:
|
||||
- files to edit
|
||||
- tests to add or update
|
||||
- risks
|
||||
```
|
||||
|
||||
## stderr
|
||||
|
||||
```text
|
||||
|
||||
```
|
||||
|
||||
## Prompt
|
||||
|
||||
```markdown
|
||||
# NightShift Agent Input
|
||||
|
||||
## System Prompt
|
||||
|
||||
You are the planning agent for NightShift.
|
||||
|
||||
Create a concise implementation plan for the current task.
|
||||
|
||||
If you need repository context before planning, output lookup requests exactly like this:
|
||||
|
||||
lookup_requests:
|
||||
- tool: read_file
|
||||
path: relative/path.py
|
||||
- tool: grep
|
||||
path: .
|
||||
pattern: search_regex
|
||||
|
||||
After context is provided, write a short plan with:
|
||||
- files to edit
|
||||
- tests to add or update
|
||||
- risks
|
||||
|
||||
Do not write code.
|
||||
|
||||
## Stage
|
||||
|
||||
- id: plan
|
||||
- type: agent
|
||||
|
||||
## Task
|
||||
|
||||
- [ ] TASK-001: Parse Lisp expressions
|
||||
|
||||
Description:
|
||||
Implement tokenization and parsing for a tiny Lisp subset.
|
||||
|
||||
Acceptance Criteria:
|
||||
- Parses numbers
|
||||
- Parses symbols
|
||||
- Parses nested lists
|
||||
- Raises useful errors for unbalanced parentheses
|
||||
- Includes unit tests
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- Parses numbers
|
||||
- Parses symbols
|
||||
- Parses nested lists
|
||||
- Raises useful errors for unbalanced parentheses
|
||||
- Includes unit tests
|
||||
|
||||
## Project Context
|
||||
|
||||
# Project Context
|
||||
|
||||
## Task Context
|
||||
|
||||
# Task Context
|
||||
|
||||
Task: `TASK-001`
|
||||
Title: Parse Lisp expressions
|
||||
|
||||
## Description
|
||||
|
||||
Implement tokenization and parsing for a tiny Lisp subset.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- Parses numbers
|
||||
- Parses symbols
|
||||
- Parses nested lists
|
||||
- Raises useful errors for unbalanced parentheses
|
||||
- Includes unit tests
|
||||
|
||||
## Previous Stage Output
|
||||
|
||||
## repo_lookup_results
|
||||
|
||||
# Repo Tool Calls
|
||||
|
||||
## 1. read_file
|
||||
|
||||
Arguments:
|
||||
- path: `lisp_parser.py`
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
File not found: lisp_parser.py
|
||||
```
|
||||
|
||||
## 2. read_file
|
||||
|
||||
Arguments:
|
||||
- path: `tests/test_lisp_parser.py`
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
File not found: tests/test_lisp_parser.py
|
||||
```
|
||||
|
||||
## 3. list_files
|
||||
|
||||
Arguments:
|
||||
- path: `.`
|
||||
- pattern: `*.py`
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
lisp.py
|
||||
tests/__init__.py
|
||||
tests/test_lisp.py
|
||||
```
|
||||
|
||||
## 4. grep
|
||||
|
||||
Arguments:
|
||||
- path: `.`
|
||||
- pattern: `def parse\(`
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
No matches found.
|
||||
```
|
||||
|
||||
## 5. grep
|
||||
|
||||
Arguments:
|
||||
- path: `.`
|
||||
- pattern: `class LispParser`
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
No matches found.
|
||||
```
|
||||
|
||||
## Retry Notes
|
||||
|
||||
- Repository lookup results have been provided. Write the final plan now; do not request more lookups.
|
||||
|
||||
## Output Contract
|
||||
|
||||
Write the requested stage output in concise markdown.
|
||||
|
||||
If you need repository context before finalizing the plan, include:
|
||||
lookup_requests:
|
||||
- tool: list_files | read_file | grep
|
||||
path: <relative path>
|
||||
pattern: <glob for list_files or regex for grep>
|
||||
|
||||
Use at most 5 lookup requests.
|
||||
Do not repeat the same lookup request.
|
||||
Prefer read_file for likely-relevant files over many grep variations.
|
||||
Do not search .nightshift, .git, virtualenvs, caches, or artifact directories.
|
||||
|
||||
NightShift will run these read-only lookup tools, save files-inspected.md, and re-run this planner stage with the retrieved context.
|
||||
```
|
||||
|
||||
|
||||
## context
|
||||
|
||||
# Context Pack
|
||||
|
||||
Task: `TASK-001`
|
||||
Title: Parse Lisp expressions
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- Parses numbers
|
||||
- Parses symbols
|
||||
- Parses nested lists
|
||||
- Raises useful errors for unbalanced parentheses
|
||||
- Includes unit tests
|
||||
|
||||
## Constraints
|
||||
|
||||
- Scoped paths: .
|
||||
- Repository lookups are read-only.
|
||||
- Excerpts are line-numbered where files are read directly.
|
||||
|
||||
## Relevant Files
|
||||
|
||||
```text
|
||||
lisp.py
|
||||
tests/__init__.py
|
||||
tests/test_lisp.py
|
||||
```
|
||||
|
||||
## Search Results
|
||||
|
||||
### Search: parse
|
||||
|
||||
```text
|
||||
tasks.md:21: Evaluate parsed arithmetic expressions.
|
||||
```
|
||||
|
||||
### Search: lisp
|
||||
|
||||
```text
|
||||
nightshift.yaml:2: name: tiny-lisp
|
||||
```
|
||||
|
||||
### Search: expressions
|
||||
|
||||
```text
|
||||
tasks.md:3: - [ ] TASK-001: Parse Lisp expressions
|
||||
tasks.md:21: Evaluate parsed arithmetic expressions.
|
||||
tasks.md:47: Implement simple truthiness and `if` expressions.
|
||||
```
|
||||
|
||||
### Search: parses
|
||||
|
||||
```text
|
||||
No matches found.
|
||||
```
|
||||
|
||||
### Search: numbers
|
||||
|
||||
```text
|
||||
tasks.md:9: - Parses numbers
|
||||
```
|
||||
|
||||
|
||||
## implement
|
||||
|
||||
diff --git a/lisp.py b/lisp.py
|
||||
index 0000000..f2a5c3d
|
||||
--- /dev/null
|
||||
+++ b/lisp.py
|
||||
@@ -0,0 +1,78 @@
|
||||
+import re
|
||||
+
|
||||
+class LispParser:
|
||||
+ def __init__(self, expression):
|
||||
+ self.expression = expression
|
||||
+ self.tokens = self.tokenize()
|
||||
+ self.index = 0
|
||||
+
|
||||
+ def tokenize(self):
|
||||
+ token_pattern = r'\s*([()])|(\d+)|([a-zA-Z_][a-zA-Z0-9_]*)'
|
||||
+ tokens = re.findall(token_pattern, self.expression)
|
||||
+ return [token for group in tokens for token in group if token]
|
||||
+
|
||||
+ def parse(self):
|
||||
+ result = []
|
||||
+ while self.index < len(self.tokens):
|
||||
+ token = self.tokens[self.index]
|
||||
+ if token == '(':
|
||||
+ self.index += 1
|
||||
+ result.append(self.parse())
|
||||
+ elif token == ')':
|
||||
+ return result
|
||||
+ else:
|
||||
+ result.append(self.parse_atom(token))
|
||||
+ self.index += 1
|
||||
+ if len(result) == 1:
|
||||
+ return result[0]
|
||||
+ return result
|
||||
+
|
||||
+ def parse_atom(self, token):
|
||||
+ try:
|
||||
+ return int(token)
|
||||
+ except ValueError:
|
||||
+ return token
|
||||
+
|
||||
+def parse_lisp(expression):
|
||||
+ parser = LispParser(expression)
|
||||
+ return parser.parse()
|
||||
+
|
||||
+if __name__ == "__main__":
|
||||
+ expression = "(1 2 (3 4) a b)"
|
||||
+ print(parse_lisp(expression))
|
||||
diff --git a/tests/test_lisp.py b/tests/test_lisp.py
|
||||
index 0000000..b5c7d8e
|
||||
--- /dev/null
|
||||
+++ b/tests/test_lisp.py
|
||||
@@ -0,0 +1,39 @@
|
||||
+import unittest
|
||||
+from lisp import parse_lisp
|
||||
+
|
||||
+class TestLispParser(unittest.TestCase):
|
||||
+ def test_parse_numbers(self):
|
||||
+ self.assertEqual(parse_lisp("123"), 123)
|
||||
+ self.assertEqual(parse_lisp("(1 2 3)"), [1, 2, 3])
|
||||
+
|
||||
+ def test_parse_symbols(self):
|
||||
+ self.assertEqual(parse_lisp("a"), "a")
|
||||
+ self.assertEqual(parse_lisp("(a b c)"), ["a", "b", "c"])
|
||||
+
|
||||
+ def test_parse_nested_lists(self):
|
||||
+ self.assertEqual(parse_lisp("(1 (2 3) 4)"), [1, [2, 3], 4])
|
||||
+ self.assertEqual(parse_lisp("((a b) (c d))"), [["a", "b"], ["c", "d"]])
|
||||
+
|
||||
+ def test_unbalanced_parentheses(self):
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp("(")
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp(")")
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp("(1 2 (3 4)")
|
||||
+
|
||||
+if __name__ == "__main__":
|
||||
+ unittest.main()
|
||||
|
||||
|
||||
## normalize
|
||||
|
||||
diff --git a/lisp.py b/lisp.py
|
||||
index 0000000..f2a5c3d
|
||||
--- /dev/null
|
||||
+++ b/lisp.py
|
||||
@@ -0,0 +1,78 @@
|
||||
+import re
|
||||
+
|
||||
+class LispParser:
|
||||
+ def __init__(self, expression):
|
||||
+ self.expression = expression
|
||||
+ self.tokens = self.tokenize()
|
||||
+ self.index = 0
|
||||
+
|
||||
+ def tokenize(self):
|
||||
+ token_pattern = r'\s*([()])|(\d+)|([a-zA-Z_][a-zA-Z0-9_]*)'
|
||||
+ tokens = re.findall(token_pattern, self.expression)
|
||||
+ return [token for group in tokens for token in group if token]
|
||||
+
|
||||
+ def parse(self):
|
||||
+ result = []
|
||||
+ while self.index < len(self.tokens):
|
||||
+ token = self.tokens[self.index]
|
||||
+ if token == '(':
|
||||
+ self.index += 1
|
||||
+ result.append(self.parse())
|
||||
+ elif token == ')':
|
||||
+ return result
|
||||
+ else:
|
||||
+ result.append(self.parse_atom(token))
|
||||
+ self.index += 1
|
||||
+ if len(result) == 1:
|
||||
+ return result[0]
|
||||
+ return result
|
||||
+
|
||||
+ def parse_atom(self, token):
|
||||
+ try:
|
||||
+ return int(token)
|
||||
+ except ValueError:
|
||||
+ return token
|
||||
+
|
||||
+def parse_lisp(expression):
|
||||
+ parser = LispParser(expression)
|
||||
+ return parser.parse()
|
||||
+
|
||||
+if __name__ == "__main__":
|
||||
+ expression = "(1 2 (3 4) a b)"
|
||||
+ print(parse_lisp(expression))
|
||||
diff --git a/tests/test_lisp.py b/tests/test_lisp.py
|
||||
index 0000000..b5c7d8e
|
||||
--- /dev/null
|
||||
+++ b/tests/test_lisp.py
|
||||
@@ -0,0 +1,39 @@
|
||||
+import unittest
|
||||
+from lisp import parse_lisp
|
||||
+
|
||||
+class TestLispParser(unittest.TestCase):
|
||||
+ def test_parse_numbers(self):
|
||||
+ self.assertEqual(parse_lisp("123"), 123)
|
||||
+ self.assertEqual(parse_lisp("(1 2 3)"), [1, 2, 3])
|
||||
+
|
||||
+ def test_parse_symbols(self):
|
||||
+ self.assertEqual(parse_lisp("a"), "a")
|
||||
+ self.assertEqual(parse_lisp("(a b c)"), ["a", "b", "c"])
|
||||
+
|
||||
+ def test_parse_nested_lists(self):
|
||||
+ self.assertEqual(parse_lisp("(1 (2 3) 4)"), [1, [2, 3], 4])
|
||||
+ self.assertEqual(parse_lisp("((a b) (c d))"), [["a", "b"], ["c", "d"]])
|
||||
+
|
||||
+ def test_unbalanced_parentheses(self):
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp("(")
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp(")")
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp("(1 2 (3 4)")
|
||||
+
|
||||
+if __name__ == "__main__":
|
||||
+ unittest.main()
|
||||
|
||||
|
||||
## validate_patch
|
||||
|
||||
# Patch Validation
|
||||
|
||||
Status: pass
|
||||
Changed files: 2
|
||||
Changed lines: 68
|
||||
|
||||
## Files
|
||||
|
||||
- `lisp.py`
|
||||
- `tests/test_lisp.py`
|
||||
|
||||
|
||||
## apply_patch
|
||||
|
||||
# Patch Apply
|
||||
|
||||
Status: fail
|
||||
Mode: apply
|
||||
Patch: `.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch`
|
||||
Command: `git apply --ignore-whitespace C:\Users\metis\Documents\tiny-lisp-nightshift\.nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\applied-2.patch`
|
||||
Exit code: 128
|
||||
|
||||
## stdout
|
||||
|
||||
```text
|
||||
|
||||
```
|
||||
|
||||
## stderr
|
||||
|
||||
```text
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:6: trailing whitespace.
|
||||
import re
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:7: trailing whitespace.
|
||||
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:8: trailing whitespace.
|
||||
class LispParser:
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:9: trailing whitespace.
|
||||
def __init__(self, expression):
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:10: trailing whitespace.
|
||||
self.expression = expression
|
||||
error: corrupt patch at line 48
|
||||
```
|
||||
|
||||
|
||||
## context-pack.md
|
||||
|
||||
# Context Pack
|
||||
|
||||
Task: `TASK-001`
|
||||
Title: Parse Lisp expressions
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- Parses numbers
|
||||
- Parses symbols
|
||||
- Parses nested lists
|
||||
- Raises useful errors for unbalanced parentheses
|
||||
- Includes unit tests
|
||||
|
||||
## Constraints
|
||||
|
||||
- Scoped paths: .
|
||||
- Repository lookups are read-only.
|
||||
- Excerpts are line-numbered where files are read directly.
|
||||
|
||||
## Relevant Files
|
||||
|
||||
```text
|
||||
lisp.py
|
||||
tests/__init__.py
|
||||
tests/test_lisp.py
|
||||
```
|
||||
|
||||
## Search Results
|
||||
|
||||
### Search: parse
|
||||
|
||||
```text
|
||||
tasks.md:21: Evaluate parsed arithmetic expressions.
|
||||
```
|
||||
|
||||
### Search: lisp
|
||||
|
||||
```text
|
||||
nightshift.yaml:2: name: tiny-lisp
|
||||
```
|
||||
|
||||
### Search: expressions
|
||||
|
||||
```text
|
||||
tasks.md:3: - [ ] TASK-001: Parse Lisp expressions
|
||||
tasks.md:21: Evaluate parsed arithmetic expressions.
|
||||
tasks.md:47: Implement simple truthiness and `if` expressions.
|
||||
```
|
||||
|
||||
### Search: parses
|
||||
|
||||
```text
|
||||
No matches found.
|
||||
```
|
||||
|
||||
### Search: numbers
|
||||
|
||||
```text
|
||||
tasks.md:9: - Parses numbers
|
||||
```
|
||||
|
||||
|
||||
## project-context-chart.md
|
||||
|
||||
# Project Context Chart
|
||||
|
||||
## Entry Points
|
||||
|
||||
- `tests/test_lisp.py`: class SmokeTests(unittest.TestCase):
|
||||
|
||||
## Tests
|
||||
|
||||
- `tests/__init__.py`
|
||||
- `tests/test_lisp.py`
|
||||
|
||||
## Files
|
||||
|
||||
### `agents/implementer.md`
|
||||
|
||||
- Responsibility: You are the implementation agent for NightShift.
|
||||
- Entry point: false
|
||||
- Test file: false
|
||||
- Functions: None detected
|
||||
- Classes: None detected
|
||||
- Anchors/search terms: None detected
|
||||
|
||||
### `agents/planner.md`
|
||||
|
||||
- Responsibility: You are the planning agent for NightShift.
|
||||
- Entry point: false
|
||||
- Test file: false
|
||||
- Functions: None detected
|
||||
- Classes: None detected
|
||||
- Anchors/search terms: None detected
|
||||
|
||||
### `agents/reviewer.md`
|
||||
|
||||
- Responsibility: You are the review agent for NightShift.
|
||||
- Entry point: false
|
||||
- Test file: false
|
||||
- Functions: None detected
|
||||
- Classes: None detected
|
||||
- Anchors/search terms: None detected
|
||||
|
||||
### `lisp.py`
|
||||
|
||||
- Responsibility: """Tiny Lisp quickstart target.
|
||||
- Entry point: false
|
||||
- Test file: false
|
||||
- Functions: None detected
|
||||
- Classes: None detected
|
||||
- Anchors/search terms: None detected
|
||||
|
||||
### `nightshift.yaml`
|
||||
|
||||
- Responsibility: project:
|
||||
- Entry point: false
|
||||
- Test file: false
|
||||
- Functions: None detected
|
||||
- Classes: None detected
|
||||
- Anchors/search terms: None detected
|
||||
|
||||
### `tasks.md`
|
||||
|
||||
- Responsibility: Tasks
|
||||
- Entry point: false
|
||||
- Test file: false
|
||||
- Functions: None detected
|
||||
- Classes: None detected
|
||||
- Anchors/search terms: None detected
|
||||
|
||||
### `tests/__init__.py`
|
||||
|
||||
- Responsibility: """Quickstart Lisp test suite."""
|
||||
- Entry point: false
|
||||
- Test file: true
|
||||
- Functions: None detected
|
||||
- Classes: None detected
|
||||
- Anchors/search terms: None detected
|
||||
|
||||
### `tests/test_lisp.py`
|
||||
|
||||
- Responsibility: class SmokeTests(unittest.TestCase):
|
||||
- Entry point: true
|
||||
- Test file: true
|
||||
- Functions: test_smoke@L5
|
||||
- Classes: SmokeTests@L4
|
||||
- Anchors/search terms: SmokeTests, test_smoke
|
||||
|
||||
## Retry Notes
|
||||
|
||||
- Context update from 'implement': Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/implementation-summary.md
|
||||
- Retry 1: stage 'validate_patch' returned fail (Patch validation failed: patch creates existing file `lisp.py`.); redirecting to 'implement'.
|
||||
- Context update from 'implement': Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-summary-1.md
|
||||
- Context update from 'apply_patch': C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:6: trailing whitespace.
|
||||
import re
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:7: trailing whitespace.
|
||||
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:8: trailing whitespace.
|
||||
class LispParser:
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:9: trailing whitespace.
|
||||
def __init__(self, expression):
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:10: trailing whitespace.
|
||||
self.expression = expression
|
||||
error: corrupt patch at line 48
|
||||
- Retry 2: stage 'apply_patch' returned fail (Patch apply failed with code 128.); redirecting to 'implement'.
|
||||
- Context update from 'implement': Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-summary-2.md
|
||||
- Context update from 'apply_patch': C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:6: trailing whitespace.
|
||||
import re
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:7: trailing whitespace.
|
||||
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:8: trailing whitespace.
|
||||
class LispParser:
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:9: trailing whitespace.
|
||||
def __init__(self, expression):
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:10: trailing whitespace.
|
||||
self.expression = expression
|
||||
error: corrupt patch at line 48
|
||||
- Retry 3: stage 'apply_patch' returned fail (Patch apply failed with code 128.); redirecting to 'implement'.
|
||||
|
||||
## Output Contract
|
||||
|
||||
Return a unified diff only, suitable for saving as proposed.patch or repair-N.patch.
|
||||
Do not include prose outside the patch.
|
||||
Use diff --git headers and hunk headers.
|
||||
For existing files, do not use new file mode or /dev/null headers.
|
||||
On repair attempts, return a complete corrected replacement diff.
|
||||
```
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
diff --git a/lisp.py b/lisp.py
|
||||
index 0000000..f2a5c3d
|
||||
--- /dev/null
|
||||
+++ b/lisp.py
|
||||
@@ -0,0 +1,78 @@
|
||||
+import re
|
||||
+
|
||||
+class LispParser:
|
||||
+ def __init__(self, expression):
|
||||
+ self.expression = expression
|
||||
+ self.tokens = self.tokenize()
|
||||
+ self.index = 0
|
||||
+
|
||||
+ def tokenize(self):
|
||||
+ token_pattern = r'\s*([()])|(\d+)|([a-zA-Z_][a-zA-Z0-9_]*)'
|
||||
+ tokens = re.findall(token_pattern, self.expression)
|
||||
+ return [token for group in tokens for token in group if token]
|
||||
+
|
||||
+ def parse(self):
|
||||
+ result = []
|
||||
+ while self.index < len(self.tokens):
|
||||
+ token = self.tokens[self.index]
|
||||
+ if token == '(':
|
||||
+ self.index += 1
|
||||
+ result.append(self.parse())
|
||||
+ elif token == ')':
|
||||
+ return result
|
||||
+ else:
|
||||
+ result.append(self.parse_atom(token))
|
||||
+ self.index += 1
|
||||
+ if len(result) == 1:
|
||||
+ return result[0]
|
||||
+ return result
|
||||
+
|
||||
+ def parse_atom(self, token):
|
||||
+ try:
|
||||
+ return int(token)
|
||||
+ except ValueError:
|
||||
+ return token
|
||||
+
|
||||
+def parse_lisp(expression):
|
||||
+ parser = LispParser(expression)
|
||||
+ return parser.parse()
|
||||
+
|
||||
+if __name__ == "__main__":
|
||||
+ expression = "(1 2 (3 4) a b)"
|
||||
+ print(parse_lisp(expression))
|
||||
diff --git a/tests/test_lisp.py b/tests/test_lisp.py
|
||||
index 0000000..b5c7d8e
|
||||
--- /dev/null
|
||||
+++ b/tests/test_lisp.py
|
||||
@@ -0,0 +1,39 @@
|
||||
+import unittest
|
||||
+from lisp import parse_lisp
|
||||
+
|
||||
+class TestLispParser(unittest.TestCase):
|
||||
+ def test_parse_numbers(self):
|
||||
+ self.assertEqual(parse_lisp("123"), 123)
|
||||
+ self.assertEqual(parse_lisp("(1 2 3)"), [1, 2, 3])
|
||||
+
|
||||
+ def test_parse_symbols(self):
|
||||
+ self.assertEqual(parse_lisp("a"), "a")
|
||||
+ self.assertEqual(parse_lisp("(a b c)"), ["a", "b", "c"])
|
||||
+
|
||||
+ def test_parse_nested_lists(self):
|
||||
+ self.assertEqual(parse_lisp("(1 (2 3) 4)"), [1, [2, 3], 4])
|
||||
+ self.assertEqual(parse_lisp("((a b) (c d))"), [["a", "b"], ["c", "d"]])
|
||||
+
|
||||
+ def test_unbalanced_parentheses(self):
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp("(")
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp(")")
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp("(1 2 (3 4)")
|
||||
+
|
||||
+if __name__ == "__main__":
|
||||
+ unittest.main()
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
diff --git a/lisp.py b/lisp.py
|
||||
index 0000000..f2a5c3d
|
||||
--- /dev/null
|
||||
+++ b/lisp.py
|
||||
@@ -0,0 +1,78 @@
|
||||
+import re
|
||||
+
|
||||
+class LispParser:
|
||||
+ def __init__(self, expression):
|
||||
+ self.expression = expression
|
||||
+ self.tokens = self.tokenize()
|
||||
+ self.index = 0
|
||||
+
|
||||
+ def tokenize(self):
|
||||
+ token_pattern = r'\s*([()])|(\d+)|([a-zA-Z_][a-zA-Z0-9_]*)'
|
||||
+ tokens = re.findall(token_pattern, self.expression)
|
||||
+ return [token for group in tokens for token in group if token]
|
||||
+
|
||||
+ def parse(self):
|
||||
+ result = []
|
||||
+ while self.index < len(self.tokens):
|
||||
+ token = self.tokens[self.index]
|
||||
+ if token == '(':
|
||||
+ self.index += 1
|
||||
+ result.append(self.parse())
|
||||
+ elif token == ')':
|
||||
+ return result
|
||||
+ else:
|
||||
+ result.append(self.parse_atom(token))
|
||||
+ self.index += 1
|
||||
+ if len(result) == 1:
|
||||
+ return result[0]
|
||||
+ return result
|
||||
+
|
||||
+ def parse_atom(self, token):
|
||||
+ try:
|
||||
+ return int(token)
|
||||
+ except ValueError:
|
||||
+ return token
|
||||
+
|
||||
+def parse_lisp(expression):
|
||||
+ parser = LispParser(expression)
|
||||
+ return parser.parse()
|
||||
+
|
||||
+if __name__ == "__main__":
|
||||
+ expression = "(1 2 (3 4) a b)"
|
||||
+ print(parse_lisp(expression))
|
||||
diff --git a/tests/test_lisp.py b/tests/test_lisp.py
|
||||
index 0000000..b5c7d8e
|
||||
--- /dev/null
|
||||
+++ b/tests/test_lisp.py
|
||||
@@ -0,0 +1,39 @@
|
||||
+import unittest
|
||||
+from lisp import parse_lisp
|
||||
+
|
||||
+class TestLispParser(unittest.TestCase):
|
||||
+ def test_parse_numbers(self):
|
||||
+ self.assertEqual(parse_lisp("123"), 123)
|
||||
+ self.assertEqual(parse_lisp("(1 2 3)"), [1, 2, 3])
|
||||
+
|
||||
+ def test_parse_symbols(self):
|
||||
+ self.assertEqual(parse_lisp("a"), "a")
|
||||
+ self.assertEqual(parse_lisp("(a b c)"), ["a", "b", "c"])
|
||||
+
|
||||
+ def test_parse_nested_lists(self):
|
||||
+ self.assertEqual(parse_lisp("(1 (2 3) 4)"), [1, [2, 3], 4])
|
||||
+ self.assertEqual(parse_lisp("((a b) (c d))"), [["a", "b"], ["c", "d"]])
|
||||
+
|
||||
+ def test_unbalanced_parentheses(self):
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp("(")
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp(")")
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp("(1 2 (3 4)")
|
||||
+
|
||||
+if __name__ == "__main__":
|
||||
+ unittest.main()
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
diff --git a/lisp.py b/lisp.py
|
||||
index 0000000..f2a5c3d
|
||||
--- /dev/null
|
||||
+++ b/lisp.py
|
||||
@@ -0,0 +1,78 @@
|
||||
+import re
|
||||
+
|
||||
+class LispParser:
|
||||
+ def __init__(self, expression):
|
||||
+ self.expression = expression
|
||||
+ self.tokens = self.tokenize()
|
||||
+ self.index = 0
|
||||
+
|
||||
+ def tokenize(self):
|
||||
+ token_pattern = r'\s*([()])|(\d+)|([a-zA-Z_][a-zA-Z0-9_]*)'
|
||||
+ tokens = re.findall(token_pattern, self.expression)
|
||||
+ return [token for group in tokens for token in group if token]
|
||||
+
|
||||
+ def parse(self):
|
||||
+ result = []
|
||||
+ while self.index < len(self.tokens):
|
||||
+ token = self.tokens[self.index]
|
||||
+ if token == '(':
|
||||
+ self.index += 1
|
||||
+ result.append(self.parse())
|
||||
+ elif token == ')':
|
||||
+ return result
|
||||
+ else:
|
||||
+ result.append(self.parse_atom(token))
|
||||
+ self.index += 1
|
||||
+ if len(result) == 1:
|
||||
+ return result[0]
|
||||
+ return result
|
||||
+
|
||||
+ def parse_atom(self, token):
|
||||
+ try:
|
||||
+ return int(token)
|
||||
+ except ValueError:
|
||||
+ return token
|
||||
+
|
||||
+def parse_lisp(expression):
|
||||
+ parser = LispParser(expression)
|
||||
+ return parser.parse()
|
||||
+
|
||||
+if __name__ == "__main__":
|
||||
+ expression = "(1 2 (3 4) a b)"
|
||||
+ print(parse_lisp(expression))
|
||||
diff --git a/tests/test_lisp.py b/tests/test_lisp.py
|
||||
index 0000000..b5c7d8e
|
||||
--- /dev/null
|
||||
+++ b/tests/test_lisp.py
|
||||
@@ -0,0 +1,39 @@
|
||||
+import unittest
|
||||
+from lisp import parse_lisp
|
||||
+
|
||||
+class TestLispParser(unittest.TestCase):
|
||||
+ def test_parse_numbers(self):
|
||||
+ self.assertEqual(parse_lisp("123"), 123)
|
||||
+ self.assertEqual(parse_lisp("(1 2 3)"), [1, 2, 3])
|
||||
+
|
||||
+ def test_parse_symbols(self):
|
||||
+ self.assertEqual(parse_lisp("a"), "a")
|
||||
+ self.assertEqual(parse_lisp("(a b c)"), ["a", "b", "c"])
|
||||
+
|
||||
+ def test_parse_nested_lists(self):
|
||||
+ self.assertEqual(parse_lisp("(1 (2 3) 4)"), [1, [2, 3], 4])
|
||||
+ self.assertEqual(parse_lisp("((a b) (c d))"), [["a", "b"], ["c", "d"]])
|
||||
+
|
||||
+ def test_unbalanced_parentheses(self):
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp("(")
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp(")")
|
||||
+ with self.assertRaises(IndexError):
|
||||
+ parse_lisp("(1 2 (3 4)")
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
# Implementation Summary
|
||||
|
||||
Stage: `implement`
|
||||
Status: pass
|
||||
Repair attempt: 1
|
||||
Patch: `.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-1.patch`
|
||||
|
||||
## Retry Feedback
|
||||
|
||||
- Context update from 'implement': Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/implementation-summary.md
|
||||
- Retry 1: stage 'validate_patch' returned fail (Patch validation failed: patch creates existing file `lisp.py`.); redirecting to 'implement'.
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
# Implementation Summary
|
||||
|
||||
Stage: `implement`
|
||||
Status: pass
|
||||
Repair attempt: 2
|
||||
Patch: `.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-2.patch`
|
||||
|
||||
## Retry Feedback
|
||||
|
||||
- Context update from 'implement': Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/implementation-summary.md
|
||||
- Retry 1: stage 'validate_patch' returned fail (Patch validation failed: patch creates existing file `lisp.py`.); redirecting to 'implement'.
|
||||
- Context update from 'implement': Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-summary-1.md
|
||||
- Context update from 'apply_patch': C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:6: trailing whitespace.
|
||||
import re
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:7: trailing whitespace.
|
||||
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:8: trailing whitespace.
|
||||
class LispParser:
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:9: trailing whitespace.
|
||||
def __init__(self, expression):
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:10: trailing whitespace.
|
||||
self.expression = expression
|
||||
error: corrupt patch at line 48
|
||||
- Retry 2: stage 'apply_patch' returned fail (Patch apply failed with code 128.); redirecting to 'implement'.
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
# Implementation Summary
|
||||
|
||||
Stage: `implement`
|
||||
Status: pass
|
||||
Repair attempt: 3
|
||||
Patch: `.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-3.patch`
|
||||
|
||||
## Retry Feedback
|
||||
|
||||
- Context update from 'apply_patch': C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:6: trailing whitespace.
|
||||
import re
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:7: trailing whitespace.
|
||||
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:8: trailing whitespace.
|
||||
class LispParser:
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:9: trailing whitespace.
|
||||
def __init__(self, expression):
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:10: trailing whitespace.
|
||||
self.expression = expression
|
||||
error: corrupt patch at line 48
|
||||
- Retry 2: stage 'apply_patch' returned fail (Patch apply failed with code 128.); redirecting to 'implement'.
|
||||
- Context update from 'implement': Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-summary-2.md
|
||||
- Context update from 'apply_patch': C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:6: trailing whitespace.
|
||||
import re
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:7: trailing whitespace.
|
||||
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:8: trailing whitespace.
|
||||
class LispParser:
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:9: trailing whitespace.
|
||||
def __init__(self, expression):
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:10: trailing whitespace.
|
||||
self.expression = expression
|
||||
error: corrupt patch at line 48
|
||||
- Retry 3: stage 'apply_patch' returned fail (Patch apply failed with code 128.); redirecting to 'implement'.
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
# Stage Results
|
||||
|
||||
Task: `TASK-001`
|
||||
Status: failed
|
||||
Retry count: 3
|
||||
Reason: Retry limit reached after stage 'apply_patch': Patch apply failed with code 128.
|
||||
|
||||
## plan
|
||||
|
||||
Status: pass
|
||||
Reason: Agent completed after repo lookup.
|
||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\plan.md
|
||||
Next stage:
|
||||
Context update:
|
||||
|
||||
## context
|
||||
|
||||
Status: pass
|
||||
Reason: Context pack written.
|
||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\context-pack.md
|
||||
Next stage:
|
||||
Context update:
|
||||
|
||||
## implement
|
||||
|
||||
Status: pass
|
||||
Reason: Proposed patch written.
|
||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\proposed.patch
|
||||
Next stage:
|
||||
Context update: Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/implementation-summary.md
|
||||
|
||||
## normalize
|
||||
|
||||
Status: pass
|
||||
Reason: Normalized patch written.
|
||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\normalized.patch
|
||||
Next stage:
|
||||
Context update:
|
||||
|
||||
## validate_patch
|
||||
|
||||
Status: fail
|
||||
Reason: Patch validation failed: patch creates existing file `lisp.py`.
|
||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\patch-validation.md
|
||||
Next stage:
|
||||
Context update:
|
||||
|
||||
## implement
|
||||
|
||||
Status: pass
|
||||
Reason: Proposed patch written.
|
||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\repair-1.patch
|
||||
Next stage:
|
||||
Context update: Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-summary-1.md
|
||||
|
||||
## normalize
|
||||
|
||||
Status: pass
|
||||
Reason: Normalized patch written.
|
||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\normalized-1.patch
|
||||
Next stage:
|
||||
Context update:
|
||||
|
||||
## validate_patch
|
||||
|
||||
Status: pass
|
||||
Reason: Patch validation passed.
|
||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\patch-validation-1.md
|
||||
Next stage:
|
||||
Context update:
|
||||
|
||||
## apply_patch
|
||||
|
||||
Status: fail
|
||||
Reason: Patch apply failed with code 128.
|
||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\patch-apply-output-1.txt
|
||||
Next stage:
|
||||
Context update: C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:6: trailing whitespace.
|
||||
import re
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:7: trailing whitespace.
|
||||
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:8: trailing whitespace.
|
||||
class LispParser:
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:9: trailing whitespace.
|
||||
def __init__(self, expression):
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:10: trailing whitespace.
|
||||
self.expression = expression
|
||||
error: corrupt patch at line 48
|
||||
|
||||
## implement
|
||||
|
||||
Status: pass
|
||||
Reason: Proposed patch written.
|
||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\repair-2.patch
|
||||
Next stage:
|
||||
Context update: Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-summary-2.md
|
||||
|
||||
## normalize
|
||||
|
||||
Status: pass
|
||||
Reason: Normalized patch written.
|
||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\normalized-2.patch
|
||||
Next stage:
|
||||
Context update:
|
||||
|
||||
## validate_patch
|
||||
|
||||
Status: pass
|
||||
Reason: Patch validation passed.
|
||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\patch-validation-2.md
|
||||
Next stage:
|
||||
Context update:
|
||||
|
||||
## apply_patch
|
||||
|
||||
Status: fail
|
||||
Reason: Patch apply failed with code 128.
|
||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\patch-apply-output-2.txt
|
||||
Next stage:
|
||||
Context update: C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:6: trailing whitespace.
|
||||
import re
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:7: trailing whitespace.
|
||||
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:8: trailing whitespace.
|
||||
class LispParser:
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:9: trailing whitespace.
|
||||
def __init__(self, expression):
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:10: trailing whitespace.
|
||||
self.expression = expression
|
||||
error: corrupt patch at line 48
|
||||
|
||||
## implement
|
||||
|
||||
Status: pass
|
||||
Reason: Proposed patch written.
|
||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\repair-3.patch
|
||||
Next stage:
|
||||
Context update: Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-summary-3.md
|
||||
|
||||
## normalize
|
||||
|
||||
Status: pass
|
||||
Reason: Normalized patch written.
|
||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\normalized-3.patch
|
||||
Next stage:
|
||||
Context update:
|
||||
|
||||
## validate_patch
|
||||
|
||||
Status: pass
|
||||
Reason: Patch validation passed.
|
||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\patch-validation-3.md
|
||||
Next stage:
|
||||
Context update:
|
||||
|
||||
## apply_patch
|
||||
|
||||
Status: fail
|
||||
Reason: Patch apply failed with code 128.
|
||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\patch-apply-output-3.txt
|
||||
Next stage:
|
||||
Context update: C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:6: trailing whitespace.
|
||||
import re
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:7: trailing whitespace.
|
||||
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:8: trailing whitespace.
|
||||
class LispParser:
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:9: trailing whitespace.
|
||||
def __init__(self, expression):
|
||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:10: trailing whitespace.
|
||||
self.expression = expression
|
||||
error: corrupt patch at line 48
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
# Task Completion
|
||||
|
||||
Task: `TASK-001`
|
||||
Pipeline status: failed
|
||||
Marked complete: false
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
- [ ] TASK-001: Parse Lisp expressions
|
||||
|
||||
Description:
|
||||
Implement tokenization and parsing for a tiny Lisp subset.
|
||||
|
||||
Acceptance Criteria:
|
||||
- Parses numbers
|
||||
- Parses symbols
|
||||
- Parses nested lists
|
||||
- Raises useful errors for unbalanced parentheses
|
||||
- Includes unit tests
|
||||
|
|
@ -113,6 +113,12 @@ nightshift status
|
|||
nightshift run --task TASK-001
|
||||
```
|
||||
|
||||
For the first real-model tutorial target:
|
||||
|
||||
```bash
|
||||
nightshift init --template imageboard --root nightshift-imageboard
|
||||
```
|
||||
|
||||
Open the read-only artifact dashboard:
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -1,95 +0,0 @@
|
|||
"""Fake code writer for the NightShift end-to-end quickstart."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import difflib
|
||||
|
||||
|
||||
FILES = {
|
||||
"lisp.py": '''"""Tiny Lisp parser used by the NightShift quickstart."""
|
||||
|
||||
|
||||
def tokenize(source):
|
||||
spaced = source.replace("(", " ( ").replace(")", " ) ")
|
||||
return spaced.split()
|
||||
|
||||
|
||||
def parse(source):
|
||||
tokens = tokenize(source)
|
||||
if not tokens:
|
||||
raise ValueError("empty expression")
|
||||
expression = _parse_tokens(tokens)
|
||||
if tokens:
|
||||
raise ValueError("unexpected trailing tokens")
|
||||
return expression
|
||||
|
||||
|
||||
def _parse_tokens(tokens):
|
||||
if not tokens:
|
||||
raise ValueError("unexpected end of input")
|
||||
token = tokens.pop(0)
|
||||
if token == "(":
|
||||
values = []
|
||||
while tokens and tokens[0] != ")":
|
||||
values.append(_parse_tokens(tokens))
|
||||
if not tokens:
|
||||
raise ValueError("unbalanced parentheses")
|
||||
tokens.pop(0)
|
||||
return values
|
||||
if token == ")":
|
||||
raise ValueError("unexpected closing parenthesis")
|
||||
return _atom(token)
|
||||
|
||||
|
||||
def _atom(token):
|
||||
try:
|
||||
return int(token)
|
||||
except ValueError:
|
||||
return token
|
||||
''',
|
||||
"tests/test_lisp.py": """import unittest
|
||||
|
||||
from lisp import parse
|
||||
|
||||
|
||||
class ParserTests(unittest.TestCase):
|
||||
def test_parses_numbers(self):
|
||||
self.assertEqual(parse("42"), 42)
|
||||
|
||||
def test_parses_symbols(self):
|
||||
self.assertEqual(parse("answer"), "answer")
|
||||
|
||||
def test_parses_nested_lists(self):
|
||||
self.assertEqual(parse("(+ 1 (* 2 3))"), ["+", 1, ["*", 2, 3]])
|
||||
|
||||
def test_rejects_unbalanced_parentheses(self):
|
||||
with self.assertRaises(ValueError):
|
||||
parse("(+ 1 2")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
""",
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
chunks: list[str] = []
|
||||
for relative_path, desired in FILES.items():
|
||||
path = Path(relative_path)
|
||||
current = path.read_text(encoding="utf-8") if path.exists() else ""
|
||||
chunks.append(f"diff --git a/{relative_path} b/{relative_path}\n")
|
||||
chunks.extend(
|
||||
difflib.unified_diff(
|
||||
current.splitlines(keepends=True),
|
||||
desired.splitlines(keepends=True),
|
||||
fromfile=f"a/{relative_path}",
|
||||
tofile=f"b/{relative_path}",
|
||||
)
|
||||
)
|
||||
print("".join(chunks), end="")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
"""Fake planner for the NightShift end-to-end quickstart."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
prompt = sys.stdin.read()
|
||||
if "repo_lookup_results" in prompt:
|
||||
print("# Plan")
|
||||
print("")
|
||||
print("- Use the context pack and inspected files.")
|
||||
print("- Add parser functions to `lisp.py`.")
|
||||
print("- Replace the smoke test with parser unit tests.")
|
||||
else:
|
||||
print("lookup_requests:")
|
||||
print("- tool: read_file")
|
||||
print(" path: lisp.py")
|
||||
print("- tool: read_file")
|
||||
print(" path: tests/test_lisp.py")
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
You are the implementation agent.
|
||||
|
||||
Output only a unified diff.
|
||||
Implement the smallest correct change and include tests when needed.
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
You are the planning agent. Create a small conservative plan.
|
||||
|
||||
Include:
|
||||
- relevant files
|
||||
- implementation steps
|
||||
- tests
|
||||
- risks
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
You are the review agent.
|
||||
|
||||
Output:
|
||||
status: pass | fail | retry | escalate
|
||||
reason: <short explanation>
|
||||
next_stage: <optional stage id>
|
||||
context_update: <compact useful note>
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
"""Tiny Lisp quickstart target.
|
||||
|
||||
NightShift tasks in this example are intended to fill this module in.
|
||||
"""
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
project:
|
||||
name: tiny-lisp
|
||||
root: .
|
||||
task_file: tasks.md
|
||||
artifact_dir: .nightshift
|
||||
|
||||
safety:
|
||||
require_clean_worktree: false
|
||||
scoped_paths:
|
||||
- .
|
||||
allowed_commands:
|
||||
- python -m unittest discover -v
|
||||
forbidden_commands:
|
||||
- rm -rf
|
||||
- git push
|
||||
- curl | bash
|
||||
|
||||
experiment:
|
||||
label: quickstart-lisp
|
||||
prompt_variant: fake-agent-v1
|
||||
|
||||
agents:
|
||||
planner:
|
||||
backend: command
|
||||
command: python agents/fake_planner.py
|
||||
system_prompt: agents/planner.md
|
||||
|
||||
implementer:
|
||||
backend: command
|
||||
command: python agents/fake_code_writer.py
|
||||
system_prompt: agents/implementer.md
|
||||
|
||||
reviewer:
|
||||
backend: command
|
||||
command: python -c "print('status: pass'); print('reason: quickstart reviewer accepted artifacts')"
|
||||
system_prompt: agents/reviewer.md
|
||||
|
||||
pipeline:
|
||||
max_task_retries: 3
|
||||
continue_on_task_failure: false
|
||||
stages:
|
||||
- id: plan
|
||||
type: agent
|
||||
agent: planner
|
||||
output: plan.md
|
||||
|
||||
- id: context
|
||||
type: repo_context
|
||||
output: context-pack.md
|
||||
|
||||
- id: implement
|
||||
type: code_writer
|
||||
agent: implementer
|
||||
output: proposed.patch
|
||||
|
||||
- id: normalize
|
||||
type: patch_normalizer
|
||||
output: normalized.patch
|
||||
|
||||
- id: validate_patch
|
||||
type: patch_validator
|
||||
output: patch-validation.md
|
||||
max_files: 4
|
||||
max_lines: 400
|
||||
on_fail: implement
|
||||
|
||||
- id: apply_patch
|
||||
type: patch_apply
|
||||
mode: apply
|
||||
output: patch-apply-output.txt
|
||||
on_fail: implement
|
||||
|
||||
- id: test
|
||||
type: command
|
||||
commands:
|
||||
- python -m unittest discover -v
|
||||
output: test-output.txt
|
||||
shell: true
|
||||
timeout_seconds: 60
|
||||
on_fail: implement
|
||||
|
||||
- id: review
|
||||
type: agent_review
|
||||
agent: reviewer
|
||||
on_fail: implement
|
||||
output: review.md
|
||||
|
||||
- id: summarize
|
||||
type: summarize
|
||||
output: final-notes.md
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
# Tasks
|
||||
|
||||
- [ ] TASK-001: Parse Lisp expressions
|
||||
|
||||
Description:
|
||||
Implement tokenization and parsing for a tiny Lisp subset.
|
||||
|
||||
Acceptance Criteria:
|
||||
- Parses numbers
|
||||
- Parses symbols
|
||||
- Parses nested lists
|
||||
- Raises useful errors for unbalanced parentheses
|
||||
- Includes unit tests
|
||||
|
||||
- [ ] TASK-002: Evaluate arithmetic forms
|
||||
|
||||
Dependencies:
|
||||
- TASK-001
|
||||
|
||||
Description:
|
||||
Evaluate parsed arithmetic expressions.
|
||||
|
||||
Acceptance Criteria:
|
||||
- Supports `+`, `-`, `*`, and `/`
|
||||
- Evaluates nested arithmetic
|
||||
- Includes unit tests
|
||||
|
||||
- [ ] TASK-003: Add variables and definitions
|
||||
|
||||
Dependencies:
|
||||
- TASK-002
|
||||
|
||||
Description:
|
||||
Add an environment and support variable lookup and definitions.
|
||||
|
||||
Acceptance Criteria:
|
||||
- Supports symbol lookup
|
||||
- Supports `(define name value)`
|
||||
- Keeps environment behavior tested
|
||||
|
|
@ -1 +0,0 @@
|
|||
"""Quickstart Lisp test suite."""
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import unittest
|
||||
|
||||
|
||||
class SmokeTests(unittest.TestCase):
|
||||
def test_smoke(self):
|
||||
self.assertTrue(True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
@ -1,22 +1,40 @@
|
|||
# Tutorial 01: Building A Small Imageboard With Real Local Models
|
||||
|
||||
This tutorial starts after the quickstart. The quickstart uses fake command agents so you can verify the pipeline deterministically. Here, you will point NightShift at a small web application and let a local model implement one feature slice at a time.
|
||||
This tutorial starts after the quickstart. The quickstart uses fake command agents so you can verify the pipeline deterministically. Here, you will use a named NightShift template and let a local model build the target application.
|
||||
|
||||
The target is a compact 4chan-style imageboard: boards, threads, replies, images, tripcodes, sessions, reports, and moderation. That is larger than a toy parser, but it is a better first real-model target because each task maps to ordinary web-app files and tests.
|
||||
The target is a compact 4chan-style imageboard: boards, threads, replies, images, tripcodes, sessions, reports, and moderation. That is larger than a toy parser, but it is a better first real-model target because failures are ordinary web-app failures: routes, schema, file handling, tests, and model helpers.
|
||||
|
||||
Keep the first run scoped to `TASK-001`. Let later tasks build on the previous completed task.
|
||||
## What The Template Creates
|
||||
|
||||
## What You Will Build
|
||||
Run this from a disposable parent directory:
|
||||
|
||||
You will create a disposable Flask project with SQLite and use NightShift to implement:
|
||||
```bash
|
||||
nightshift init --template imageboard --root nightshift-imageboard
|
||||
cd nightshift-imageboard
|
||||
```
|
||||
|
||||
1. Board and thread data model, routes, SQLite schema, and tests.
|
||||
2. Image upload and thumbnail generation.
|
||||
3. Bump ordering and reply counters.
|
||||
4. Tripcodes and session cookies.
|
||||
5. Moderation and report queue.
|
||||
The template creates:
|
||||
|
||||
NightShift still controls the workflow. The model proposes code; NightShift validates, applies, tests, records artifacts, and shows the result in the dashboard.
|
||||
```text
|
||||
nightshift.yaml
|
||||
.nightshift/
|
||||
agents/
|
||||
planner.md
|
||||
implementer.md
|
||||
reviewer.md
|
||||
tasks.md
|
||||
src/
|
||||
imageboard/
|
||||
tests/
|
||||
templates/
|
||||
static/
|
||||
uploads/
|
||||
thumbs/
|
||||
```
|
||||
|
||||
NightShift control files live in `.nightshift/`. Application source lives under `src/`, tests under `tests/`, HTML templates under `templates/`, and image/static files under `static/`.
|
||||
|
||||
The template does not create the Flask app, schema, models, or tests. That is intentional: `TASK-001` asks the agent to create the initial application slice. This is a better exercise of NightShift than pre-building the app and asking for a small patch.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
|
|
@ -26,7 +44,7 @@ Install NightShift from this repository:
|
|||
python -m pip install -e .
|
||||
```
|
||||
|
||||
Install runtime dependencies for the target project:
|
||||
Install target project dependencies:
|
||||
|
||||
```bash
|
||||
python -m pip install flask pillow pytest
|
||||
|
|
@ -41,371 +59,65 @@ ollama list
|
|||
|
||||
NightShift uses Ollama's local HTTP API, normally at `http://localhost:11434`.
|
||||
|
||||
## 1. Create A Scratch Target Project
|
||||
## Task Plan
|
||||
|
||||
Do not run apply-mode experiments directly inside the NightShift repo. Create a disposable project.
|
||||
The template writes this task sequence to `.nightshift/tasks.md`.
|
||||
|
||||
PowerShell:
|
||||
1. Board and thread foundation
|
||||
Implement the initial data model, SQLite schema, model helpers, `/board/<name>` and `/thread/<id>` routes, and tests.
|
||||
|
||||
```powershell
|
||||
$TargetProject = "$HOME\Documents\nightshift-imageboard"
|
||||
New-Item -ItemType Directory -Force $TargetProject
|
||||
Set-Location $TargetProject
|
||||
New-Item -ItemType Directory -Force agents, tests, static\uploads, static\thumbs, templates
|
||||
```
|
||||
2. Image upload and thumbnails
|
||||
Add image attachments, metadata, upload storage under `static/uploads`, and thumbnails under `static/thumbs`.
|
||||
|
||||
Bash:
|
||||
3. Bump ordering and reply counts
|
||||
Sort board threads by most recent reply and maintain reply counters.
|
||||
|
||||
```bash
|
||||
mkdir -p ~/nightshift-imageboard/{agents,tests,static/uploads,static/thumbs,templates}
|
||||
cd ~/nightshift-imageboard
|
||||
```
|
||||
4. Tripcodes and session cookies
|
||||
Add anonymous names, optional tripcodes, and lightweight poster identity.
|
||||
|
||||
## 2. Add The Starter App
|
||||
5. Moderation and report queue
|
||||
Add reporting, report queue, dismiss actions, and post hiding.
|
||||
|
||||
Create `app.py`:
|
||||
Run only `TASK-001` first. Do not ask the model to implement uploads, tripcodes, or moderation until the previous tasks pass.
|
||||
|
||||
```python
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import sqlite3
|
||||
|
||||
from flask import Flask, abort, g, redirect, render_template_string, request, url_for
|
||||
|
||||
|
||||
DATABASE = "imageboard.db"
|
||||
|
||||
|
||||
def create_app(database: str | None = None) -> Flask:
|
||||
app = Flask(__name__)
|
||||
app.config["DATABASE"] = database or DATABASE
|
||||
app.config["UPLOAD_DIR"] = Path("static/uploads")
|
||||
app.config["THUMB_DIR"] = Path("static/thumbs")
|
||||
app.secret_key = "dev-secret"
|
||||
|
||||
@app.before_request
|
||||
def open_db() -> None:
|
||||
g.db = sqlite3.connect(app.config["DATABASE"])
|
||||
g.db.row_factory = sqlite3.Row
|
||||
|
||||
@app.teardown_request
|
||||
def close_db(_exc: BaseException | None) -> None:
|
||||
db = g.pop("db", None)
|
||||
if db is not None:
|
||||
db.close()
|
||||
|
||||
@app.get("/")
|
||||
def index():
|
||||
return redirect(url_for("board", name="test"))
|
||||
|
||||
@app.get("/board/<name>")
|
||||
def board(name: str):
|
||||
abort(501)
|
||||
|
||||
@app.get("/thread/<int:thread_id>")
|
||||
def thread(thread_id: int):
|
||||
abort(501)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
create_app().run(debug=True)
|
||||
```
|
||||
|
||||
Create `schema.sql`:
|
||||
|
||||
```sql
|
||||
-- NightShift will fill this in during TASK-001.
|
||||
```
|
||||
|
||||
Create `models.py`:
|
||||
|
||||
```python
|
||||
"""Database helpers for the imageboard tutorial."""
|
||||
```
|
||||
|
||||
Create `tests/test_app.py`:
|
||||
|
||||
```python
|
||||
from app import create_app
|
||||
|
||||
|
||||
def test_index_redirects_to_test_board(tmp_path):
|
||||
app = create_app(str(tmp_path / "test.db"))
|
||||
client = app.test_client()
|
||||
|
||||
response = client.get("/")
|
||||
|
||||
assert response.status_code == 302
|
||||
assert response.headers["Location"].endswith("/board/test")
|
||||
```
|
||||
|
||||
## 3. Add NightShift Config
|
||||
|
||||
Create `nightshift.yaml`:
|
||||
|
||||
```yaml
|
||||
project:
|
||||
name: imageboard
|
||||
root: .
|
||||
task_file: tasks.md
|
||||
artifact_dir: .nightshift
|
||||
|
||||
safety:
|
||||
require_clean_worktree: false
|
||||
scoped_paths:
|
||||
- .
|
||||
allowed_commands:
|
||||
- python -m pytest -q
|
||||
forbidden_commands:
|
||||
- rm -rf
|
||||
- git push
|
||||
- curl | bash
|
||||
|
||||
experiment:
|
||||
label: imageboard-real-model
|
||||
prompt_variant: ollama-qwen25-coder-14b-v1
|
||||
|
||||
agents:
|
||||
planner:
|
||||
backend: ollama
|
||||
model: qwen2.5-coder:14b
|
||||
temperature: 0.2
|
||||
system_prompt: agents/planner.md
|
||||
|
||||
implementer:
|
||||
backend: ollama
|
||||
model: qwen2.5-coder:14b
|
||||
temperature: 0.1
|
||||
system_prompt: agents/implementer.md
|
||||
|
||||
reviewer:
|
||||
backend: ollama
|
||||
model: qwen2.5-coder:14b
|
||||
temperature: 0.1
|
||||
system_prompt: agents/reviewer.md
|
||||
|
||||
pipeline:
|
||||
max_task_retries: 3
|
||||
continue_on_task_failure: false
|
||||
stages:
|
||||
- id: plan
|
||||
type: agent
|
||||
agent: planner
|
||||
output: plan.md
|
||||
|
||||
- id: context
|
||||
type: repo_context
|
||||
output: context-pack.md
|
||||
|
||||
- id: implement
|
||||
type: file_writer
|
||||
agent: implementer
|
||||
output: proposed.patch
|
||||
|
||||
- id: normalize
|
||||
type: patch_normalizer
|
||||
output: normalized.patch
|
||||
|
||||
- id: validate_patch
|
||||
type: patch_validator
|
||||
output: patch-validation.md
|
||||
max_files: 8
|
||||
max_lines: 700
|
||||
on_fail: implement
|
||||
|
||||
- id: apply_patch
|
||||
type: patch_apply
|
||||
mode: apply
|
||||
output: patch-apply-output.txt
|
||||
on_fail: implement
|
||||
|
||||
- id: test
|
||||
type: command
|
||||
commands:
|
||||
- python -m pytest -q
|
||||
output: test-output.txt
|
||||
shell: true
|
||||
timeout_seconds: 20
|
||||
on_fail: implement
|
||||
|
||||
- id: review
|
||||
type: agent_review
|
||||
agent: reviewer
|
||||
on_fail: implement
|
||||
output: review.md
|
||||
|
||||
- id: summarize
|
||||
type: summarize
|
||||
output: final-notes.md
|
||||
```
|
||||
|
||||
## 4. Add Agent Prompts
|
||||
|
||||
Create `agents/planner.md`:
|
||||
|
||||
```markdown
|
||||
You are the planning agent for NightShift.
|
||||
|
||||
Create a concise implementation plan for the current task.
|
||||
|
||||
If you need repository context before planning, output lookup requests exactly like this:
|
||||
|
||||
lookup_requests:
|
||||
- tool: read_file
|
||||
path: relative/path.py
|
||||
- tool: grep
|
||||
path: .
|
||||
pattern: search_regex
|
||||
|
||||
After context is provided, write a short plan with:
|
||||
- files to edit
|
||||
- tests to add or update
|
||||
- risks
|
||||
|
||||
Do not write code.
|
||||
```
|
||||
|
||||
Create `agents/implementer.md`:
|
||||
|
||||
````markdown
|
||||
You are the implementation agent for NightShift.
|
||||
|
||||
Output only complete file content blocks.
|
||||
Use one fenced block per file with this exact opening form:
|
||||
```file:relative/path.py
|
||||
<complete file content>
|
||||
```
|
||||
Do not include explanations before or after the file blocks.
|
||||
Include tests when needed.
|
||||
Keep the change as small as possible.
|
||||
Only edit files needed for the task.
|
||||
````
|
||||
|
||||
Create `agents/reviewer.md`:
|
||||
|
||||
```markdown
|
||||
You are the review agent for NightShift.
|
||||
|
||||
Review the task, plan, patch artifacts, test output, and final state.
|
||||
|
||||
Output exactly:
|
||||
|
||||
status: pass | fail | retry | escalate
|
||||
reason: <short explanation>
|
||||
next_stage: <optional stage id>
|
||||
context_update: <compact useful note>
|
||||
|
||||
Use retry when the implementation is close but needs another patch.
|
||||
Use fail when the patch is unsafe, unrelated, or clearly broken.
|
||||
Use pass only when the acceptance criteria are satisfied.
|
||||
```
|
||||
|
||||
## 5. Add The Task List
|
||||
|
||||
Create `tasks.md`:
|
||||
|
||||
```markdown
|
||||
# Tasks
|
||||
|
||||
- [ ] TASK-001: Board and thread foundation
|
||||
|
||||
Description:
|
||||
Implement the initial imageboard data model and read routes. Add a SQLite schema and model helpers for boards, threads, and replies. Implement `/board/<name>` and `/thread/<id>` routes with simple HTML responses. Include tests that initialize a temporary database, create board/thread/reply records, and verify both routes.
|
||||
|
||||
Acceptance Criteria:
|
||||
- Defines SQLite tables for boards, threads, and replies
|
||||
- Provides database initialization and model helper functions
|
||||
- Implements `/board/<name>` route showing threads for that board
|
||||
- Implements `/thread/<id>` route showing the thread and replies
|
||||
- Includes route and model tests using a temporary database
|
||||
|
||||
- [ ] TASK-002: Image upload and thumbnails
|
||||
|
||||
Dependencies:
|
||||
- TASK-001
|
||||
|
||||
Description:
|
||||
Add image attachment support for new threads and replies. Store uploaded image metadata in SQLite, save uploaded files under `static/uploads`, and generate thumbnails under `static/thumbs`.
|
||||
|
||||
Acceptance Criteria:
|
||||
- Accepts image uploads for threads and replies
|
||||
- Stores image filename, thumbnail filename, MIME type, and size
|
||||
- Generates thumbnails with Pillow
|
||||
- Rejects unsupported or oversized files
|
||||
- Includes upload and thumbnail tests
|
||||
|
||||
- [ ] TASK-003: Bump ordering and reply counts
|
||||
|
||||
Dependencies:
|
||||
- TASK-002
|
||||
|
||||
Description:
|
||||
Sort board threads by most recent bump. Creating a reply updates the thread bump timestamp and increments reply counters.
|
||||
|
||||
Acceptance Criteria:
|
||||
- Board pages sort threads by latest bump time
|
||||
- Replies increment thread reply count
|
||||
- Reply creation updates bump timestamp
|
||||
- Tests cover ordering and counters
|
||||
|
||||
- [ ] TASK-004: Tripcodes and session cookies
|
||||
|
||||
Dependencies:
|
||||
- TASK-003
|
||||
|
||||
Description:
|
||||
Add anonymous names, optional tripcodes, and a session cookie for lightweight poster identity.
|
||||
|
||||
Acceptance Criteria:
|
||||
- Supports optional name and tripcode input
|
||||
- Stores tripcode hashes without storing raw tripcode secrets
|
||||
- Sets and reuses a poster session cookie
|
||||
- Displays stable poster identity on posts
|
||||
- Includes tripcode and session tests
|
||||
|
||||
- [ ] TASK-005: Moderation and report queue
|
||||
|
||||
Dependencies:
|
||||
- TASK-004
|
||||
|
||||
Description:
|
||||
Add post reporting and a simple moderation queue. Moderators can view reports, dismiss reports, and hide reported posts.
|
||||
|
||||
Acceptance Criteria:
|
||||
- Users can report threads and replies
|
||||
- Reports are stored with reason and timestamp
|
||||
- Moderation queue lists open reports
|
||||
- Moderation actions can dismiss reports or hide posts
|
||||
- Includes moderation and report queue tests
|
||||
```
|
||||
|
||||
## 6. Validate And Run
|
||||
## Validate And Run
|
||||
|
||||
Validate the project:
|
||||
|
||||
```bash
|
||||
python -m nightshift.cli validate --config nightshift.yaml
|
||||
nightshift validate
|
||||
```
|
||||
|
||||
Run only the first task:
|
||||
Run the first task:
|
||||
|
||||
```bash
|
||||
python -m nightshift.cli run --config nightshift.yaml --task TASK-001
|
||||
nightshift run --task TASK-001
|
||||
```
|
||||
|
||||
Start the dashboard:
|
||||
|
||||
```bash
|
||||
python -m nightshift.cli web --config nightshift.yaml --host 127.0.0.1 --port 8765
|
||||
nightshift web --host 127.0.0.1 --port 8765
|
||||
```
|
||||
|
||||
Open `http://127.0.0.1:8765/`.
|
||||
|
||||
## Notes On Scope
|
||||
## What To Inspect
|
||||
|
||||
This is still a non-trivial first project. The advantage over a tiny interpreter is that failures are ordinary web-app failures: missing routes, schema mistakes, file handling, or tests. Those are easier to inspect in NightShift artifacts than parser recursion or tokenizer loops.
|
||||
Useful artifacts:
|
||||
|
||||
Keep the tasks sequential. Do not ask the model to implement uploads, tripcodes, or moderation before `TASK-001` is passing.
|
||||
```text
|
||||
.nightshift/runs/<run-id>/devlog.md
|
||||
.nightshift/runs/<run-id>/run.log
|
||||
.nightshift/runs/<run-id>/tasks/TASK-001/plan.md
|
||||
.nightshift/runs/<run-id>/tasks/TASK-001/context-pack.md
|
||||
.nightshift/runs/<run-id>/tasks/TASK-001/proposed.patch
|
||||
.nightshift/runs/<run-id>/tasks/TASK-001/test-output.txt
|
||||
.nightshift/runs/<run-id>/tasks/TASK-001/final-notes.md
|
||||
```
|
||||
|
||||
The dashboard surfaces `devlog.md`, run status, logs, and links to finer-grained artifacts.
|
||||
|
||||
## Scope Note
|
||||
|
||||
This project is still not tiny. The advantage over a language interpreter is that the model works in familiar web-app territory and failures are easier to inspect. If `TASK-001` fails, read `devlog.md`, `test-output.txt`, and the implementer output before increasing retries or changing prompts.
|
||||
|
|
|
|||
4
lisp.py
4
lisp.py
|
|
@ -1,4 +0,0 @@
|
|||
"""Tiny Lisp quickstart target.
|
||||
|
||||
NightShift tasks in this example are intended to fill this module in.
|
||||
"""
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
project:
|
||||
name: tiny-lisp
|
||||
root: .
|
||||
task_file: tasks.md
|
||||
artifact_dir: .nightshift
|
||||
|
||||
safety:
|
||||
require_clean_worktree: false
|
||||
scoped_paths:
|
||||
- .
|
||||
allowed_commands:
|
||||
- python -m unittest discover -v
|
||||
forbidden_commands:
|
||||
- rm -rf
|
||||
- git push
|
||||
- curl | bash
|
||||
|
||||
agents:
|
||||
planner:
|
||||
backend: ollama
|
||||
model: qwen2.5-coder:14b
|
||||
temperature: 0.2
|
||||
system_prompt: agents/planner.md
|
||||
|
||||
implementer:
|
||||
backend: ollama
|
||||
model: qwen2.5-coder:14b
|
||||
temperature: 0.1
|
||||
system_prompt: agents/implementer.md
|
||||
|
||||
reviewer:
|
||||
backend: ollama
|
||||
model: qwen2.5-coder:14b
|
||||
temperature: 0.1
|
||||
system_prompt: agents/reviewer.md
|
||||
|
||||
pipeline:
|
||||
max_task_retries: 3
|
||||
continue_on_task_failure: false
|
||||
stages:
|
||||
- id: plan
|
||||
type: agent
|
||||
agent: planner
|
||||
output: plan.md
|
||||
|
||||
- id: context
|
||||
type: repo_context
|
||||
output: context-pack.md
|
||||
|
||||
- id: implement
|
||||
type: file_writer
|
||||
agent: implementer
|
||||
output: proposed.patch
|
||||
|
||||
- id: normalize
|
||||
type: patch_normalizer
|
||||
output: normalized.patch
|
||||
|
||||
- id: validate_patch
|
||||
type: patch_validator
|
||||
output: patch-validation.md
|
||||
max_files: 4
|
||||
max_lines: 400
|
||||
on_fail: implement
|
||||
|
||||
- id: apply_patch
|
||||
type: patch_apply
|
||||
mode: apply
|
||||
output: patch-apply-output.txt
|
||||
on_fail: implement
|
||||
|
||||
- id: test
|
||||
type: command
|
||||
commands:
|
||||
- python -m unittest discover -v
|
||||
output: test-output.txt
|
||||
shell: true
|
||||
timeout_seconds: 60
|
||||
on_fail: implement
|
||||
|
||||
- id: review
|
||||
type: agent_review
|
||||
agent: reviewer
|
||||
on_fail: implement
|
||||
output: review.md
|
||||
|
||||
- id: summarize
|
||||
type: summarize
|
||||
output: final-notes.md
|
||||
|
|
@ -30,6 +30,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
|
||||
init_parser = subparsers.add_parser("init", help="Create starter NightShift files.")
|
||||
init_parser.add_argument("--root", default=".", help="Directory to initialize.")
|
||||
init_parser.add_argument(
|
||||
"--template",
|
||||
default="basic",
|
||||
choices=("basic", "imageboard"),
|
||||
help="Starter template to create.",
|
||||
)
|
||||
init_parser.add_argument("--force", action="store_true", help="Overwrite existing starter files.")
|
||||
|
||||
validate_parser = subparsers.add_parser("validate", help="Validate nightshift.yaml.")
|
||||
|
|
@ -57,7 +63,7 @@ def main(argv: list[str] | None = None) -> int:
|
|||
|
||||
try:
|
||||
if args.command == "init":
|
||||
written = init_project(Path(args.root), force=args.force)
|
||||
written = init_project(Path(args.root), force=args.force, template=args.template)
|
||||
print("Created NightShift starter files:")
|
||||
for path in written:
|
||||
print(f"- {path}")
|
||||
|
|
|
|||
|
|
@ -16,15 +16,38 @@ STARTER_FILES = {
|
|||
"agents/reviewer.md": templates.REVIEWER_PROMPT,
|
||||
}
|
||||
|
||||
IMAGEBOARD_FILES = {
|
||||
"nightshift.yaml": templates.IMAGEBOARD_NIGHTSHIFT_YAML,
|
||||
".nightshift/tasks.md": templates.IMAGEBOARD_TASKS_MD,
|
||||
".nightshift/agents/planner.md": templates.REAL_MODEL_PLANNER_PROMPT,
|
||||
".nightshift/agents/implementer.md": templates.REAL_MODEL_IMPLEMENTER_PROMPT,
|
||||
".nightshift/agents/reviewer.md": templates.REAL_MODEL_REVIEWER_PROMPT,
|
||||
"README.md": templates.IMAGEBOARD_README,
|
||||
"src/imageboard/.gitkeep": "",
|
||||
"tests/.gitkeep": "",
|
||||
"templates/.gitkeep": "",
|
||||
"static/uploads/.gitkeep": "",
|
||||
"static/thumbs/.gitkeep": "",
|
||||
}
|
||||
|
||||
def init_project(root: Path, force: bool = False) -> list[Path]:
|
||||
PROJECT_TEMPLATES = {
|
||||
"basic": STARTER_FILES,
|
||||
"imageboard": IMAGEBOARD_FILES,
|
||||
}
|
||||
|
||||
|
||||
def init_project(root: Path, force: bool = False, template: str = "basic") -> list[Path]:
|
||||
"""Create starter NightShift files under root.
|
||||
|
||||
Existing files are left untouched unless force is true.
|
||||
"""
|
||||
|
||||
root = root.resolve()
|
||||
targets = [root / relative for relative in STARTER_FILES]
|
||||
if template not in PROJECT_TEMPLATES:
|
||||
known = ", ".join(sorted(PROJECT_TEMPLATES))
|
||||
raise InitError(f"Unknown template '{template}'. Available templates: {known}")
|
||||
files = PROJECT_TEMPLATES[template]
|
||||
targets = [root / relative for relative in files]
|
||||
existing = [path for path in targets if path.exists()]
|
||||
if existing and not force:
|
||||
formatted = ", ".join(str(path.relative_to(root)) for path in existing)
|
||||
|
|
@ -34,7 +57,7 @@ def init_project(root: Path, force: bool = False) -> list[Path]:
|
|||
)
|
||||
|
||||
written: list[Path] = []
|
||||
for relative, content in STARTER_FILES.items():
|
||||
for relative, content in files.items():
|
||||
path = root / relative
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(content, encoding="utf-8")
|
||||
|
|
|
|||
|
|
@ -122,3 +122,256 @@ reason: <short explanation>
|
|||
next_stage: <optional stage id>
|
||||
context_update: <compact useful note>
|
||||
"""
|
||||
|
||||
IMAGEBOARD_NIGHTSHIFT_YAML = """project:
|
||||
name: imageboard
|
||||
root: .
|
||||
task_file: .nightshift/tasks.md
|
||||
artifact_dir: .nightshift
|
||||
|
||||
safety:
|
||||
require_clean_worktree: false
|
||||
scoped_paths:
|
||||
- src
|
||||
- tests
|
||||
- templates
|
||||
- static
|
||||
- schema.sql
|
||||
- pyproject.toml
|
||||
allowed_commands:
|
||||
- python -m pytest -q
|
||||
forbidden_commands:
|
||||
- rm -rf
|
||||
- git push
|
||||
- curl | bash
|
||||
|
||||
experiment:
|
||||
label: imageboard-real-model
|
||||
prompt_variant: ollama-qwen25-coder-14b-v1
|
||||
|
||||
agents:
|
||||
planner:
|
||||
backend: ollama
|
||||
model: qwen2.5-coder:14b
|
||||
temperature: 0.2
|
||||
system_prompt: .nightshift/agents/planner.md
|
||||
|
||||
implementer:
|
||||
backend: ollama
|
||||
model: qwen2.5-coder:14b
|
||||
temperature: 0.1
|
||||
system_prompt: .nightshift/agents/implementer.md
|
||||
|
||||
reviewer:
|
||||
backend: ollama
|
||||
model: qwen2.5-coder:14b
|
||||
temperature: 0.1
|
||||
system_prompt: .nightshift/agents/reviewer.md
|
||||
|
||||
pipeline:
|
||||
max_task_retries: 3
|
||||
continue_on_task_failure: false
|
||||
stages:
|
||||
- id: plan
|
||||
type: agent
|
||||
agent: planner
|
||||
output: plan.md
|
||||
|
||||
- id: context
|
||||
type: repo_context
|
||||
output: context-pack.md
|
||||
|
||||
- id: implement
|
||||
type: file_writer
|
||||
agent: implementer
|
||||
output: proposed.patch
|
||||
|
||||
- id: normalize
|
||||
type: patch_normalizer
|
||||
output: normalized.patch
|
||||
|
||||
- id: validate_patch
|
||||
type: patch_validator
|
||||
output: patch-validation.md
|
||||
max_files: 10
|
||||
max_lines: 900
|
||||
on_fail: implement
|
||||
|
||||
- id: apply_patch
|
||||
type: patch_apply
|
||||
mode: apply
|
||||
output: patch-apply-output.txt
|
||||
on_fail: implement
|
||||
|
||||
- id: test
|
||||
type: command
|
||||
commands:
|
||||
- python -m pytest -q
|
||||
output: test-output.txt
|
||||
shell: true
|
||||
timeout_seconds: 20
|
||||
on_fail: implement
|
||||
|
||||
- id: review
|
||||
type: agent_review
|
||||
agent: reviewer
|
||||
on_fail: implement
|
||||
output: review.md
|
||||
|
||||
- id: summarize
|
||||
type: summarize
|
||||
output: final-notes.md
|
||||
"""
|
||||
|
||||
IMAGEBOARD_TASKS_MD = """# Tasks
|
||||
|
||||
- [ ] TASK-001: Board and thread foundation
|
||||
|
||||
Description:
|
||||
Create the initial Flask imageboard application. Implement the board and thread data model, SQLite schema, model helpers, `/board/<name>` and `/thread/<id>` routes, and tests. Keep source code under `src/`, tests under `tests/`, HTML templates under `templates/`, and static files under `static/`.
|
||||
|
||||
Acceptance Criteria:
|
||||
- Defines SQLite tables for boards, threads, and replies
|
||||
- Provides database initialization and model helper functions
|
||||
- Implements `/board/<name>` route showing threads for that board
|
||||
- Implements `/thread/<id>` route showing the thread and replies
|
||||
- Includes route and model tests using a temporary database
|
||||
|
||||
- [ ] TASK-002: Image upload and thumbnails
|
||||
|
||||
Dependencies:
|
||||
- TASK-001
|
||||
|
||||
Description:
|
||||
Add image attachment support for new threads and replies. Store uploaded image metadata in SQLite, save uploaded files under `static/uploads`, and generate thumbnails under `static/thumbs`.
|
||||
|
||||
Acceptance Criteria:
|
||||
- Accepts image uploads for threads and replies
|
||||
- Stores image filename, thumbnail filename, MIME type, and size
|
||||
- Generates thumbnails with Pillow
|
||||
- Rejects unsupported or oversized files
|
||||
- Includes upload and thumbnail tests
|
||||
|
||||
- [ ] TASK-003: Bump ordering and reply counts
|
||||
|
||||
Dependencies:
|
||||
- TASK-002
|
||||
|
||||
Description:
|
||||
Sort board threads by most recent bump. Creating a reply updates the thread bump timestamp and increments reply counters.
|
||||
|
||||
Acceptance Criteria:
|
||||
- Board pages sort threads by latest bump time
|
||||
- Replies increment thread reply count
|
||||
- Reply creation updates bump timestamp
|
||||
- Tests cover ordering and counters
|
||||
|
||||
- [ ] TASK-004: Tripcodes and session cookies
|
||||
|
||||
Dependencies:
|
||||
- TASK-003
|
||||
|
||||
Description:
|
||||
Add anonymous names, optional tripcodes, and a session cookie for lightweight poster identity.
|
||||
|
||||
Acceptance Criteria:
|
||||
- Supports optional name and tripcode input
|
||||
- Stores tripcode hashes without storing raw tripcode secrets
|
||||
- Sets and reuses a poster session cookie
|
||||
- Displays stable poster identity on posts
|
||||
- Includes tripcode and session tests
|
||||
|
||||
- [ ] TASK-005: Moderation and report queue
|
||||
|
||||
Dependencies:
|
||||
- TASK-004
|
||||
|
||||
Description:
|
||||
Add post reporting and a simple moderation queue. Moderators can view reports, dismiss reports, and hide reported posts.
|
||||
|
||||
Acceptance Criteria:
|
||||
- Users can report threads and replies
|
||||
- Reports are stored with reason and timestamp
|
||||
- Moderation queue lists open reports
|
||||
- Moderation actions can dismiss reports or hide posts
|
||||
- Includes moderation and report queue tests
|
||||
"""
|
||||
|
||||
REAL_MODEL_PLANNER_PROMPT = """You are the planning agent for NightShift.
|
||||
|
||||
Create a concise implementation plan for the current task.
|
||||
|
||||
If you need repository context before planning, output lookup requests exactly like this:
|
||||
|
||||
lookup_requests:
|
||||
- tool: read_file
|
||||
path: relative/path.py
|
||||
- tool: grep
|
||||
path: .
|
||||
pattern: search_regex
|
||||
|
||||
After context is provided, write a short plan with:
|
||||
- files to edit
|
||||
- tests to add or update
|
||||
- risks
|
||||
|
||||
Do not write code.
|
||||
"""
|
||||
|
||||
REAL_MODEL_IMPLEMENTER_PROMPT = """You are the implementation agent for NightShift.
|
||||
|
||||
Output only complete file content blocks.
|
||||
Use one fenced block per file with this exact opening form:
|
||||
```file:relative/path.py
|
||||
<complete file content>
|
||||
```
|
||||
Do not include explanations before or after the file blocks.
|
||||
Include tests when needed.
|
||||
Keep the change as small as possible.
|
||||
Only edit files needed for the task.
|
||||
"""
|
||||
|
||||
REAL_MODEL_REVIEWER_PROMPT = """You are the review agent for NightShift.
|
||||
|
||||
Review the task, plan, patch artifacts, test output, and final state.
|
||||
|
||||
Output exactly:
|
||||
|
||||
status: pass | fail | retry | escalate
|
||||
reason: <short explanation>
|
||||
next_stage: <optional stage id>
|
||||
context_update: <compact useful note>
|
||||
|
||||
Use retry when the implementation is close but needs another patch.
|
||||
Use fail when the patch is unsafe, unrelated, or clearly broken.
|
||||
Use pass only when the acceptance criteria are satisfied.
|
||||
"""
|
||||
|
||||
IMAGEBOARD_README = """# NightShift Imageboard Target
|
||||
|
||||
This project was created with:
|
||||
|
||||
```bash
|
||||
nightshift init --template imageboard
|
||||
```
|
||||
|
||||
NightShift control files live in `.nightshift/`. Target application code should live under `src/`, tests under `tests/`, templates under `templates/`, and uploaded/generated static files under `static/`.
|
||||
|
||||
Install target dependencies:
|
||||
|
||||
```bash
|
||||
python -m pip install flask pillow pytest
|
||||
```
|
||||
|
||||
Validate the project:
|
||||
|
||||
```bash
|
||||
nightshift validate
|
||||
```
|
||||
|
||||
Run the first task:
|
||||
|
||||
```bash
|
||||
nightshift run --task TASK-001
|
||||
```
|
||||
"""
|
||||
|
|
|
|||
52
tasks.md
52
tasks.md
|
|
@ -1,52 +0,0 @@
|
|||
# Tasks
|
||||
|
||||
- [ ] TASK-001: Parse Lisp expressions
|
||||
|
||||
Description:
|
||||
Implement tokenization and parsing for a tiny Lisp subset.
|
||||
|
||||
Acceptance Criteria:
|
||||
- Parses numbers
|
||||
- Parses symbols
|
||||
- Parses nested lists
|
||||
- Raises useful errors for unbalanced parentheses
|
||||
- Includes unit tests
|
||||
|
||||
- [ ] TASK-002: Evaluate arithmetic forms
|
||||
|
||||
Dependencies:
|
||||
- TASK-001
|
||||
|
||||
Description:
|
||||
Evaluate parsed arithmetic expressions.
|
||||
|
||||
Acceptance Criteria:
|
||||
- Supports `+`, `-`, `*`, and `/`
|
||||
- Evaluates nested arithmetic
|
||||
- Includes unit tests
|
||||
|
||||
- [ ] TASK-003: Add variables and definitions
|
||||
|
||||
Dependencies:
|
||||
- TASK-002
|
||||
|
||||
Description:
|
||||
Add an environment and support variable lookup and definitions.
|
||||
|
||||
Acceptance Criteria:
|
||||
- Supports symbol lookup
|
||||
- Supports `(define name value)`
|
||||
- Keeps environment behavior tested
|
||||
|
||||
- [ ] TASK-004: Add conditionals
|
||||
|
||||
Dependencies:
|
||||
- TASK-003
|
||||
|
||||
Description:
|
||||
Implement simple truthiness and `if` expressions.
|
||||
|
||||
Acceptance Criteria:
|
||||
- Supports `(if condition then else)`
|
||||
- Handles false-like values consistently
|
||||
- Includes tests for both branches
|
||||
|
|
@ -38,6 +38,27 @@ class InitProjectTests(unittest.TestCase):
|
|||
|
||||
self.assertIn("TASK-001", (root / "tasks.md").read_text(encoding="utf-8"))
|
||||
|
||||
def test_init_imageboard_template_creates_control_and_source_dirs(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
|
||||
written = init_project(root, template="imageboard")
|
||||
|
||||
self.assertIn(root / "nightshift.yaml", written)
|
||||
self.assertTrue((root / ".nightshift" / "tasks.md").exists())
|
||||
self.assertTrue((root / ".nightshift" / "agents" / "planner.md").exists())
|
||||
self.assertTrue((root / "src" / "imageboard" / ".gitkeep").exists())
|
||||
self.assertTrue((root / "tests" / ".gitkeep").exists())
|
||||
self.assertIn(
|
||||
"task_file: .nightshift/tasks.md",
|
||||
(root / "nightshift.yaml").read_text(encoding="utf-8"),
|
||||
)
|
||||
|
||||
def test_init_rejects_unknown_template(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
with self.assertRaisesRegex(InitError, "Unknown template"):
|
||||
init_project(Path(directory), template="missing")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user